Skip to content

Conversation

@freyacodes
Copy link
Contributor

This fixes a bug where <textarea> would render with many carets. Reports vary, but in my case it would would be at the start and end of every "word", as long as those cursors came before the actual cursor.

The actual cursor would render normally, except that it does not render immediately after a line break. This appears to be a separate issue.

The issue occurs because push_glyph_store_to_unbreakable_segment gets passed too long of a range. This bypasses the check here:

if insertion_point_index >= range.begin() &&
insertion_point_index <= range.end() &&
(range.begin() != insertion_point_index || range.begin().0 == 0)

Testing: Tested with ./mach test-unit. No new tests were added. Perhaps someone has an idea of how one could test this. Also tested manually in multiple scenarios, including with a text input. I was not able to get Servo to soft wrap in a textbox, maybe Servo does not support that currently. Ultimately I am not super confident about this solution as it interacts with line wrapping, and I would like to hear some feedback
Fixes: #40075

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Nov 23, 2025
Signed-off-by: Freya Arbjerg <git@arbjerg.dev>
@yezhizhen yezhizhen added the T-linux-wpt Do a try run of the WPT label Nov 23, 2025
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Nov 23, 2025
@github-actions
Copy link

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

@github-actions
Copy link

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

Flaky unexpected result (30)
  • OK /FileAPI/url/url-with-fetch.any.html (#21517)
    • FAIL [expected PASS] subtest: Revoke blob URL after calling fetch, fetch should succeed

      promise_test: Unhandled rejection with value: object "TypeError: Network error occurred"
      

  • OK /IndexedDB/idbfactory_open.any.html
    • FAIL [expected PASS] subtest: Calling open() with version argument 1.5 should not throw.

      assert_equals: version expected 1 but got 9007199254740991
      

  • 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
      

  • OK /_mozilla/webxr/create_session.https.html
    • FAIL [expected PASS] subtest: create_session

      can't access property "simulateDeviceConnection", navigator.xr.test is undefined
      

  • CRASH [expected OK] /cookiestore/idlharness.https.any.worker.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-041.html
  • OK /css/css-cascade/layer-cssom-order-reverse.html (#36094)
    • FAIL [expected PASS] subtest: Insert layer invalidates @font-face

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

  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • 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 ui-monospace (drawing text in a canvas)
  • ERROR [expected PASS] /css/css-transforms/transform-input-004.html
  • OK /fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.html (#40478)
    • PASS [expected FAIL] subtest: Permissions policy header: "deferred-fetch=*" allows fetchLater() in the top-level document.
  • TIMEOUT [expected CRASH] /fetch/metadata/window-open.https.sub.html (#40339)
  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • 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 /html/browsers/history/the-history-interface/traverse_the_history_3.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • CRASH [expected OK] /html/browsers/the-window-object/open-close/open_initial_size.html
  • CRASH [expected OK] /html/dom/render-blocking/element-render-blocking-015.html
  • ERROR [expected OK] /html/semantics/embedded-content/media-elements/event_timeupdate_noautoplay.html (#25046)
  • OK /html/semantics/embedded-content/media-elements/preserves-pitch.html (#40352)
    • FAIL [expected PASS] subtest: Speed-ups should not change the pitch when preservesPitch=true

      assert_approx_equals: The actual pitch should be close to the expected pitch. expected 440 +/- 66 but got 0
      

  • OK [expected TIMEOUT] /html/semantics/embedded-content/media-elements/src_object_blob.html (#40340)
    • PASS [expected TIMEOUT] subtest: HTMLMediaElement.srcObject blob
  • OK /html/semantics/forms/form-submission-0/text-plain.window.html (#28687)
    • FAIL [expected PASS] subtest: text/plain: Basic File test (normal form)

      assert_equals: expected "basic=file-test.txt\r\n" but got ""
      

    • PASS [expected FAIL] subtest: text/plain: 0x00 in name (formdata event)
  • OK /html/semantics/forms/form-submission-0/urlencoded2.window.html (#28687)
    • FAIL [expected PASS] subtest: application/x-www-form-urlencoded: Basic File test (normal form)

      assert_equals: expected "basic=file-test.txt" but got ""
      

    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: 0x00 in name (normal form)
    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: 0x00 in value (formdata event)
    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: backslash in name (formdata event)
  • OK /preload/preload-invalid-resources.html (#39091)
    • PASS [expected FAIL] subtest: Preloading an invalid image (missing) should preload and not re-fetch
  • OK /reporting/same-origin-same-site-credentials.https.sub.html (#40479)
    • FAIL [expected PASS] subtest: Reporting endpoints received credentials.

      assert_equals: No additional cookies were received expected 4 but got 5
      

  • TIMEOUT /resource-timing/test_resource_timing.https.html (#25216)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (img)
  • CRASH [expected OK] /trusted-types/Node-multiple-arguments.html
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?01-05 (#38975)
    • TIMEOUT [expected PASS] subtest: Navigate a window via anchor with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Navigate a window via anchor with javascript:-urls w/ default policy in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via anchor with javascript:-urls in enforcing mode.
  • 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] /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!\""
      

    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission 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 form-submission 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!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy making the URL invalid in enforcing mode.

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

  • CRASH [expected OK] /uievents/mouse/mouse_boundary_events_modifier_no_mouse_movement.html?Alt
  • CRASH [expected OK] /upgrade-insecure-requests/gen/sharedworker-classic-data.meta/upgrade/xhr.https.html
Stable unexpected results that are known to be intermittent (25)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.

      Test timed out
      

  • OK /IndexedDB/idbobjectstore_getAll.any.html (#39276)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • OK /IndexedDB/idbobjectstore_getAll.any.worker.html (#39400)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • 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)
  • TIMEOUT [expected OK] /credential-management/credentialscontainer-frame-basics.https.html (#39430)
    • TIMEOUT [expected FAIL] subtest: navigator.credentials should be undefined in documents generated from data: URLs.

      Test timed out
      

  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai)
  • FAIL [expected PASS] /css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html (#40833)
  • FAIL [expected PASS] /css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html (#40834)
  • FAIL [expected PASS] /css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002f.html (#40830)
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-dest
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Cross-site

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-site destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination
  • CRASH [expected OK] /fetch/metadata/generated/element-iframe.https.sub.html (#40341)
  • OK /fetch/metadata/generated/element-img-environment-change.https.sub.html (#30111)
    • FAIL [expected PASS] subtest: sec-fetch-site - Same site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - Cross-Site -&gt; Same-Site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-site - Cross-Site -&gt; Cross-Site, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-site - Same-Site -&gt; Same-Site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Site -&gt; Cross-Site, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-user - no attributes
  • OK /fetch/metadata/generated/element-img-environment-change.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Not sent to non-trustworthy same-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-mode - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy same-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-dest - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-user - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - HTTPS downgrade-upgrade, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • 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/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html (#39703)
    • FAIL [expected PASS] subtest: Meta refresh of the original iframe is not blocked if moved into a sandboxed iframe

      uncaught exception: Error: assert_unreached: The iframe into which the meta was moved must not refresh Reached unreachable code
      

  • OK [expected CRASH] /html/semantics/forms/the-fieldset-element/disabled-003.html (#31730, #39631)
  • TIMEOUT [expected ERROR] /html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html (#40347)
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • FAIL [expected PASS] subtest: Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd

      assert_true: Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd expected true got false
      

  • CRASH [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#40418)
  • OK /preload/preload-error.sub.html (#37177)
    • PASS [expected FAIL] subtest: CORS (fetch): main
  • OK [expected ERROR] /resource-timing/cors-preflight.any.html (#28694)
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • TIMEOUT [expected FAIL] 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 FAIL] 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.

@github-actions
Copy link

✨ Try run (#19613347355) succeeded.

@freyacodes
Copy link
Contributor Author

CC @jdm

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Nov 23, 2025
@Taym95
Copy link
Member

Taym95 commented Nov 23, 2025

This will make it possible to edit Github PR easily @mrego 🎉

@mrobinson
Copy link
Member

Note that this line of code used to be exactly the same but was changed in #36461. I'm quite suspicious of all of the caret code.

@freyacodes
Copy link
Contributor Author

Note that this line of code used to be exactly the same but was changed in #36461. I'm quite suspicious of all of the caret code.

I'm not a big fan of it either. Yesterday I was trying to shoehorn carets into empty (\ln) lines to allow for missing carets as part of #40839. This has proven difficult with the design of layout_into_line_items() (which this PR addresses) and LineItem::TextRun. Still trying to solve that.

My testing has obviously been biased because I only write in Latin script languages. I think I will play around with other scripts and ZWS and see if I encounter any issues with this PR.

@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Nov 24, 2025
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Nov 24, 2025
@github-actions
Copy link

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

@github-actions
Copy link

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

Flaky unexpected result (41)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.

      Test timed out
      

  • OK /IndexedDB/idbfactory_open.any.html
    • FAIL [expected PASS] subtest: Calling open() with version argument 1.5 should not throw.

      assert_equals: version expected 1 but got 9007199254740991
      

  • OK /_mozilla/webxr/create_session.https.html
    • FAIL [expected PASS] subtest: create_session

      can't access property "simulateDeviceConnection", navigator.xr.test is undefined
      

  • OK /_mozilla/webxr/obtain_frame.https.html
    • FAIL [expected PASS] subtest: obtain_frame

      promise_test: Unhandled rejection with value: object "TypeError: can't access property "simulateDeviceConnection", navigator.xr.test is undefined"
      

  • ERROR [expected TIMEOUT] /_mozilla/webxr/sessionavailable.https.html
  • CRASH [expected OK] /_webgl/conformance/canvas/canvas-zero-size.html
  • CRASH [expected ERROR] /_webgl/conformance/extensions/oes-texture-float-with-canvas.html
  • 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 10 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
      

  • CRASH [expected OK] /credential-management/idlharness.https.window.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-041.html
  • FAIL [expected PASS] /css/css-backgrounds/border-image-repeat-space-9.html
  • 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 /custom-elements/form-associated/ElementInternals-setFormValue.html (#29174)
    • PASS [expected FAIL] subtest: Null value should submit nothing
  • 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 /fetch/content-length/api-and-duplicate-headers.any.worker.html (#35197)
    • FAIL [expected PASS] subtest: fetch() and duplicate Content-Length/Content-Type headers

      promise_test: Unhandled rejection with value: object "TypeError: Network error occurred"
      

  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • TIMEOUT [expected CRASH] /html/anonymous-iframe/indexeddb.tentative.https.window.html (#39254)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html (#20768)
    • PASS [expected FAIL] subtest: Tests that a fragment navigation in the unload handler will not block the initial navigation
  • OK /html/browsers/browsing-the-web/navigating-across-documents/refresh/same-document-refresh.html (#34597)
    • FAIL [expected PASS] subtest: Same-Document Referrer from Refresh

      assert_equals: original page loads expected "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh-with-section.sub.html?url=%23section" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/refresh/resources/refresh-with-section.sub.html?url=%23section#section"
      

  • CRASH [expected OK] /html/browsers/the-window-object/open-close/open-features-non-integer-top.html
  • CRASH [expected OK] /html/browsers/the-window-object/open-close/open_initial_size.html
  • CRASH [expected OK] /html/canvas/element/canvas-context/2d.canvas.context.invalid.args.html
  • CRASH [expected OK] /html/dom/elements/global-attributes/dir-bdi-script.html
  • OK /html/semantics/forms/form-submission-0/multipart-formdata.window.html (#28725)
    • PASS [expected FAIL] subtest: multipart/form-data: 0x00 in name (normal form)
    • PASS [expected FAIL] subtest: multipart/form-data: 0x00 in value (formdata event)
  • OK /html/semantics/forms/form-submission-0/text-plain.window.html (#28687)
    • FAIL [expected PASS] subtest: text/plain: Basic test (formdata event)

      assert_equals: expected "basic=test\r\n" but got ""
      

    • FAIL [expected PASS] subtest: text/plain: Basic File test (normal form)

      assert_equals: expected "basic=file-test.txt\r\n" but got ""
      

  • OK [expected CRASH] /html/semantics/forms/the-fieldset-element/disabled-003.html (#31730, #39631)
  • OK /html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.html (#33948)
    • FAIL [expected PASS] subtest: Revoking a blob URL immediately after calling import will not fail

      promise_test: Unhandled rejection with value: object "TypeError: Dynamic import failed"
      

  • OK /html/webappapis/user-prompts/print-during-unload.html (#35944)
    • FAIL [expected PASS] subtest: print() during unload

      assert_array_equals: expected property 1 to be "destination" but got "error: window.print is not a function" (expected array ["start", "destination"] got ["start", "error: window.print is not a function"])
      

  • OK /preload/preload-xhr.html (#39092)
    • FAIL [expected PASS] subtest: Make an XHR request immediately after creating link rel=preload.

      assert_equals: resources/dummy.xml?token=e5f88d41-049a-4ec9-b3ae-41aff9ca7c9a expected 1 but got 0
      

  • TIMEOUT [expected OK] /resource-timing/nested-context-navigations-iframe.html (#24311)
    • TIMEOUT [expected PASS] subtest: Test that cross-site iframe navigations are not observable by the parent, even after history navigations by the parent

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Test that iframe navigations are not observable by the parent
    • NOTRUN [expected PASS] subtest: Test that crossorigin iframe navigations are not observable by the parent
    • NOTRUN [expected PASS] subtest: Test that cross-site iframe navigations are not observable by the parent
    • NOTRUN [expected PASS] subtest: Test that iframe refreshes are not observable by the parent
    • NOTRUN [expected PASS] subtest: Test that crossorigin iframe refreshes are not observable by the parent
    • NOTRUN [expected PASS] subtest: Test that cross-site iframe refreshes are not observable by the parent
  • TIMEOUT /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (img)
  • CRASH [expected OK] /trusted-types/Element-setAttribute-setAttributeNS-sinks.tentative.html
  • 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] /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission 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 form-submission 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!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ a default policy making the URL invalid in enforcing mode.

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

  • CRASH [expected OK] /trusted-types/trusted-types-reporting-check-report-DedicatedWorker-sink-mismatch.html
  • CRASH [expected OK] /upgrade-insecure-requests/gen/srcdoc-inherit.meta/unset/sharedworker-classic.https.html
  • CRASH [expected OK] /webaudio/the-audio-api/processing-model/feedback-delay-time.html
  • CRASH [expected OK] /webaudio/the-audio-api/the-audioworklet-interface/simple-input-output.https.html
  • 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 (35)
  • OK /IndexedDB/idbobjectstore_getAll.any.html (#39276)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • OK /IndexedDB/idbobjectstore_getAll.any.worker.html (#39400)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • OK /IndexedDB/transaction-deactivation-timing.any.worker.html (#38808)
    • PASS [expected FAIL] subtest: New transactions are deactivated before next task
  • 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
      

  • 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
      

  • TIMEOUT /content-security-policy/inheritance/location-reload.html (#38983)
    • PASS [expected FAIL] subtest: location.reload() of empty iframe.
  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • 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 50 but got 30
      

    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(nastaliq)
  • FAIL [expected PASS] /css/css-grid/grid-lanes/tentative/alignment/row-grid-lanes-align-self-003.html (#40833)
  • FAIL [expected PASS] /css/css-grid/grid-lanes/tentative/grid-placement/row-explicit-placement-004.html (#40834)
  • FAIL [expected PASS] /css/css-grid/masonry/tentative/subgrid/column/masonry-subgrid-002f.html (#40830)
  • OK /fetch/api/credentials/cookies.any.html (#40672)
    • FAIL [expected PASS] subtest: Include mode: 1 cookie

      assert_equals: Request include cookie(s) expected "a=1" but got "a=1; domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: Include mode: 2 cookies

      assert_equals: Request include cookie(s) expected "b=2; c=3" but got "b=2; c=3; domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: Omit mode: no cookie is stored

      assert_false: Request does not have cookie(s) expected false got true
      

    • FAIL [expected PASS] subtest: Same-origin mode: 1 cookie

      assert_equals: Request include cookie(s) expected "a=1" but got "a=1; domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: Same-origin mode: 2 cookies

      assert_equals: Request include cookie(s) expected "b=2; c=3" but got "b=2; c=3; domain-attribute-matches-host=0"
      

  • OK /fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.html (#40478)
    • PASS [expected FAIL] subtest: Permissions policy header: "deferred-fetch=*" allows fetchLater() in the top-level document.
  • OK /fetch/fetch-later/send-on-discard/not-send-after-abort.https.window.html (#40696)
    • FAIL [expected PASS] subtest: A discarded document does not send an already aborted fetchLater request.

      assert_equals: Number of sent beacons does not match expected count: expected 1 but got 0
      

  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Cross-site

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-site destination

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination
  • CRASH [expected OK] /fetch/metadata/generated/element-iframe.https.sub.html (#40341)
  • OK /fetch/metadata/generated/element-img-environment-change.https.sub.html (#30111)
    • FAIL [expected PASS] subtest: sec-fetch-site - Same site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - Same-Origin -&gt; Same-Site -&gt; Same-Origin redirect, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - Cross-Site -&gt; Same-Site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - Same-Origin -&gt; Cross-Site, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Site -&gt; Cross-Site, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-mode - attributes: crossorigin

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-mode - attributes: crossorigin=use-credentials

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-user - no attributes
  • OK /fetch/metadata/generated/element-img-environment-change.sub.html (#30111)
    • FAIL [expected PASS] subtest: sec-fetch-mode - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy same-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-dest - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-dest - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-user - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • PASS [expected FAIL] subtest: sec-fetch-user - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - HTTPS downgrade-upgrade, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • 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_3.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • 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])
      

  • CRASH [expected OK] /html/browsers/the-window-object/open-close/creating_browsing_context_test_01.html (#29046)
  • OK [expected TIMEOUT] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • FAIL [expected TIMEOUT] subtest: &lt;dialog&gt;-contained autofocus element gets focused when the dialog is shown

      assert_equals: expected "DIV" but got "BODY"
      

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

      assert_equals: expected "SPAN" but got "BODY"
      

    • PASS [expected NOTRUN] subtest: Non-HTMLElement should not support autofocus
    • TIMEOUT [expected NOTRUN] subtest: Host element with delegatesFocus should support autofocus

      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
      

  • TIMEOUT [expected ERROR] /html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html (#40347)
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • FAIL [expected PASS] subtest: Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd

      assert_true: Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd expected true got false
      

  • CRASH [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#40418)
  • OK /preload/preload-error.sub.html (#37177)
    • FAIL [expected PASS] subtest: success (fetch): main

      assert_greater_than: http://web-platform.test:8000/preload/resources/dummy.xml?label=fetch should be loaded expected a number greater than 0 but got 0
      

    • FAIL [expected PASS] subtest: 404 (fetch): main

      assert_greater_than: http://web-platform.test:8000/preload/resources/dummy.xml?pipe=status%28404%29&amp;label=fetch should be loaded expected a number greater than 0 but got 0
      

    • PASS [expected FAIL] subtest: CORS (fetch): main
  • OK /preload/preload-invalid-resources.html (#39091)
    • PASS [expected FAIL] subtest: Preloading an invalid image (missing) should preload and not re-fetch
  • OK /resource-timing/buffer-full-add-then-clear.html (#40819)
    • FAIL [expected PASS] subtest: Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one

      assert_equals: Number of entries does not match the expected value. expected 3 but got 0
      

  • OK [expected ERROR] /resource-timing/cors-preflight.any.html (#28694)
  • OK /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • PASS [expected FAIL] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.
    • PASS [expected FAIL] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)

@github-actions
Copy link

✨ Try run (#19631988998) succeeded.

@freyacodes
Copy link
Contributor Author

After debugging the layout crate further (#40839 is very complicated) i've grown more confident in the change in this PR.

I think I would like to add a few reftests for the caret placement, either as part of this PR or my next.

@jdm
Copy link
Member

jdm commented Nov 29, 2025

I tested the crashing testcases from #36449 with this change and could not reproduce any crashes, or any other input weirdness.

@jdm
Copy link
Member

jdm commented Nov 29, 2025

This change makes sense to me:

  • we're processing all of the shaped glyph runs for a given text run
  • ServoRange::new's arguments are start & length
  • The range passed to push_glyph_store_to_unbreakable_segment is used to compute a range of selected text within the text run by computing an intersection with a selected range
  • The selected range is misleading, because it also doubles as the caret position if no text is selected (it just has a length of 0 in that case)
  • the current code passes a range for the entire text run, so this makes each glyph run intersect with the text run's selection range (read: caret position) and create a TextRunLineItem with a range representing a caret position

With the change in this PR, we pass a range that represents only the current glyph run, so the intersection with the text run's selection range is actually meaningful and we only get one TextRunLineItem that has a caret position included.

@jdm
Copy link
Member

jdm commented Nov 29, 2025

I can't think of how to write reftests for this without exposing additional APIs for manipulating the cursor position in text inputs. A testdriver.js test would be able to do it by sending keyboard inputs to the focused text input, but those tests can't be used as reference tests with screenshots. I would:

That being said, I'm having trouble figuring out how to write the actual reference part of the test right now so that it's different from the actual content being tested.

@freyacodes
Copy link
Contributor Author

That being said, I'm having trouble figuring out how to write the actual reference part of the test right now so that it's different from the actual content being tested.

I was talking with a friend about WPT testing and she brought up reftests. It seems that she meant to talk about visual tests. I've read that visual tests are generally discouraged as they are rarely run. Would a visual test be relevant?

I can't think of how to write reftests for this without exposing additional APIs for manipulating the cursor position in text inputs. A testdriver.js test would be able to do it by sending keyboard inputs to the focused text input, but those tests can't be used as reference tests with screenshots. I would:

Why not use these two functions?

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement/setSelectionRange

Both functions seem to work on <textarea> in Servo

@jdm
Copy link
Member

jdm commented Nov 30, 2025

Aha, yeah, I haven't come across https://web-platform-tests.org/writing-tests/visual.html before. I don't think we'll get any use out of that, unfortunately; anything that isn't run as part of our automated CI runs is doomed to be ignored.

Copy link
Member

@jdm jdm left a comment

Choose a reason for hiding this comment

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

There's no good story for how to create an automated test for this change, so let's get it into the hands of manual testing instead!

@jdm jdm added this pull request to the merge queue Nov 30, 2025
@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 Nov 30, 2025
Merged via the queue into servo:main with commit adc1698 Nov 30, 2025
108 checks passed
@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 Nov 30, 2025
@freyacodes freyacodes deleted the multiple-caret-fix branch November 30, 2025 19:41
@mrego
Copy link
Member

mrego commented Dec 1, 2025

One way to test carets on WPT is to use the caret-animation CSS property, setting it to manual you make sure the caret doesn't blink and you can create reference tests.

Only Chromium supports it so far, and I have no clue how easy/complex would be to support it in Servo, maybe initially adding some basic support so the caret doesn't blink would be good enough for these testing purposes.

@freyacodes
Copy link
Contributor Author

One way to test carets on WPT is to use the caret-animation CSS property, setting it to manual you make sure the caret doesn't blink and you can create reference tests.

Only Chromium supports it so far, and I have no clue how easy/complex would be to support it in Servo, maybe initially adding some basic support so the caret doesn't blink would be good enough for these testing purposes.

Well this one is easy, the caret doesn't actually blink in Servo. It would be good to add if we do ever create a reftest/visual test.

Though it still doesn't answer how to actually create a reference for the reftest.

@mrego
Copy link
Member

mrego commented Dec 1, 2025

Though it still doesn't answer how to actually create a reference for the reftest.

Both the reference and the test would have to use caret-animation, the test could use WebDriver to move the caret with arrow keys, and the reference using the selection API to place the caret (though maybe that's not implemented yet).

@freyacodes
Copy link
Contributor Author

Both the test and the reference would render the same with or without this bug. This bug was independent of how the cursor was moved

@mrego
Copy link
Member

mrego commented Dec 2, 2025

True, I haven't tested it extensively, it might be the case that for this bug in particular both would have the same output. I was thinking more on how to test the caret position in general and suggesting some idea, that was all.

I guess for this bug in particular implementing some more new things like caret-shape: block, then using Ahem font, it could be eventually possible to do a reftest. But it's totally fine that we don't have a test for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Many extra carets in textarea

7 participants