Skip to content

Conversation

@mrobinson
Copy link
Member

Instead of storing the Minibrowser egui struct in the
RunningAppState which is used for both headed and headless windows,
move it to the headed window implementation. This addresses a lot of
bits of the code that would need to test the existance of the
Minibrowser even when it should have always existed.

In addition, event handling for the headed window is moved into the
headed window implementation itself, which eliminates the need to expose
so many methods in WindowPortsMethods.

One downside is that it is impossible to support tracing of AppEvents
as accessibilty events are not cloneable. This seems like a small price
to pay for this cleanup though.

This is the first step toward supporting multiple windows in servoshell.

Testing: This should not change behavior and is thus covered by existing tests.

@mrobinson mrobinson requested a review from atbrakhi as a code owner November 21, 2025 12:19
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Nov 21, 2025
@mrobinson mrobinson requested a review from mukilan November 21, 2025 12:19
@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Nov 21, 2025
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Nov 21, 2025
@github-actions
Copy link

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

@yezhizhen
Copy link
Member

I don't think CI WPT tests can cover this change.

@mrobinson
Copy link
Member Author

I don't think CI WPT tests can cover this change.

I just wanted to ensure that my changes did not interfere with functioning of headless mode which is used by WPT.

Copy link
Member

@yezhizhen yezhizhen left a comment

Choose a reason for hiding this comment

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

This make things much cleaner.

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

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

Flaky unexpected result (33)
  • OK /FileAPI/url/url-with-fetch.any.worker.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/mozilla/getBoundingClientRect.html (#39668)
    • FAIL [expected PASS] subtest: getBoundingClientRect 1

      assert_equals: expected 62 but got 60.35
      

  • 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"
      

  • 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
      

  • TIMEOUT /content-security-policy/inheritance/location-reload.html (#38983)
    • PASS [expected FAIL] subtest: location.reload() of empty iframe.
  • OK [expected TIMEOUT] /cookiestore/httponly_cookies.https.window.html (#40697)
    • FAIL [expected TIMEOUT] subtest: HttpOnly cookies are not observed

      assert_equals: HttpOnly cookie we wrote using HTTP in cookie jar is invisible to script expected (undefined) undefined but got (string) "cspViolationReportCookie1=1"
      

    • FAIL [expected NOTRUN] subtest: HttpOnly cookies can not be set by document.cookie

      assert_equals: Trying to store an HttpOnly cookie with document.cookie fails expected "cookie1=value1; cookie3=value3" but got "HTTPONLY-cookie=value; cookie1=value1; cookie3=value3"
      

    • FAIL [expected NOTRUN] subtest: HttpOnly cookies can not be set by CookieStore

      assert_equals: httpOnly is not an option for CookieStore.set() expected "cookie1=value1; cookie2=value2; cookie3=value3" but got "HTTPONLY-cookie=value; cookie1=value1; cookie2=value2; cookie3=value3"
      

    • FAIL [expected NOTRUN] subtest: HttpOnly cookies are not deleted/overwritten

      assert_equals: HttpOnly cookie is not deleted expected (string) "HTTPONLY-cookie=value" but got (undefined) undefined
      

  • CRASH [expected OK] /cookiestore/idlharness.https.any.worker.html
  • 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-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted sans-serif (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 ui-monospace (drawing text in a canvas)
  • OK /custom-elements/form-associated/ElementInternals-setFormValue.html (#29174)
    • PASS [expected FAIL] subtest: Single value - empty name exists
    • PASS [expected FAIL] subtest: Single value - Non-empty name exists
  • ERROR [expected OK] /fetch/api/response/response-body-read-task-handling.html
    • PASS [expected FAIL] subtest: reading from a body stream should occur in a microtask scope
  • OK /fetch/fetch-later/permissions-policy/deferred-fetch-allowed-by-permissions-policy.https.window.html (#40478)
    • FAIL [expected PASS] subtest: Permissions policy header: "deferred-fetch=*" allows fetchLater() in the top-level document.

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

  • 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 /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-iframe-contentWindow.html (#28681)
    • FAIL [expected PASS] subtest: load & pageshow events do not fire on contentWindow of <iframe> element created with src='about:blank'

      assert_unreached: load should not be fired Reached unreachable code
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.html (#29056)
    • FAIL [expected PASS] subtest: Cross-origin navigation started from unload handler must be ignored

      promise_test: Unhandled rejection with value: object "SecurityError: The operation is insecure."
      

  • 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"
      

  • OK /html/browsers/history/the-history-interface/traverse_the_history_5.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • CRASH [expected OK] /html/canvas/element/compositing/2d.composite.uncovered.nocontext.destination-in.html
  • OK /html/dom/documents/resource-metadata-management/document-cookie.html (#40673)
    • FAIL [expected PASS] subtest: document has no cookie

      assert_equals: expected "" but got "domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: document.cookie

      assert_equals: expected "" but got "domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: document.cookie 1

      assert_equals: expected "a=b" but got "a=b; domain-attribute-matches-host=0"
      

    • FAIL [expected PASS] subtest: document.cookie 2

      assert_equals: expected "" but got "a=b; domain-attribute-matches-host=0"
      

  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/document-with-fragment-top.html (#28259)
    • TIMEOUT [expected FAIL] subtest: Autofocus elements in top-level browsing context's documents with "top" fragments should work.

      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/forms/form-submission-0/multipart-formdata.window.html (#28725)
    • 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 ""
      

    • PASS [expected FAIL] subtest: text/plain: 0x00 in value (normal form)
    • PASS [expected FAIL] subtest: text/plain: \r\n in name (normal form)
  • OK [expected CRASH] /html/semantics/forms/the-fieldset-element/disabled-003.html (#31730, #39631)
  • 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"])
      

  • OK [expected ERROR] /html/user-activation/no-activation-thru-escape-key.html (#40343)
  • CRASH [expected OK] /mimesniff/sniffing/html.window.html (#40471)
  • CRASH [expected OK] /pointerevents/pointerevent_multiple_pointerover_no_pointer_movement.html
  • CRASH [expected OK] /trusted-types/Node-multiple-arguments.html
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
  • CRASH [expected OK] /uievents/idlharness.window.html
  • CRASH [expected OK] /wasm/webapi/esm-integration/worklet-import-source-phase.tentative.https.html
Stable unexpected results that are known to be intermittent (27)
  • 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
  • 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 /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(khmer-mul)

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

  • OK /fetch/fetch-later/new-window.https.window.html (#32036)
    • FAIL [expected PASS] subtest: A same-origin window[target=''][features=''] can trigger fetchLater.

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

    • FAIL [expected PASS] subtest: A same-origin window[target=''][features='popup'] can trigger fetchLater.

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

    • FAIL [expected PASS] subtest: A same-origin window[target='_blank'][features=''] can trigger fetchLater.

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

    • FAIL [expected PASS] subtest: A same-origin window[target='_blank'][features='popup'] can trigger fetchLater.

      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)
    • PASS [expected FAIL] subtest: sec-fetch-user
  • 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-site destination
    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination

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

  • OK /fetch/metadata/generated/element-img-environment-change.https.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Origin -> Cross-Site -> Same-Origin redirect, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-site - Same-Origin -> Same-Site -> 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 -> 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 -> Same Origin, 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 -> Cross-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-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-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-storage-access - Same site, 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
    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy same-origin destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-mode - Not sent to non-trustworthy cross-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-dest - Not sent to non-trustworthy cross-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-user - Not sent to non-trustworthy cross-site destination, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination, no attributes
  • ERROR /fetch/metadata/generated/serviceworker.https.sub.html (#36247)
    • FAIL [expected PASS] subtest: sec-fetch-site - Same origin, no options - registration

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

  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • FAIL [expected PASS] subtest: Link with onclick navigation and href navigation

      assert_equals: expected "href" but got "click"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html (#20768)
    • FAIL [expected PASS] subtest: Tests that a fragment navigation in the unload handler will not block the initial navigation

      assert_equals: expected "" but got "#fragment"
      

  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • FAIL [expected PASS] subtest: opener of discarded auxiliary browsing context

      assert_object_equals: property "get" expected function "function opener() {
          [native code]
      }" got function "function opener() {
          [native code]
      }"
      

  • ERROR [expected OK] /html/infrastructure/common-dom-interfaces/collections/domstringlist.html (#40665)
  • OK [expected TIMEOUT] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • FAIL [expected TIMEOUT] subtest: <dialog>-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"
      

    • TIMEOUT [expected NOTRUN] subtest: Non-HTMLElement should not support autofocus

      Test timed out
      

  • 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
      

    • PASS [expected FAIL] subtest: 404 (fetch): main
    • PASS [expected FAIL] subtest: CORS (fetch): main
  • 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.
  • ERROR [expected OK] /workers/baseurl/alpha/sharedworker-in-worker.html (#21315)

@github-actions
Copy link

✨ Try run (#19570325686) succeeded.

@mrobinson mrobinson added this pull request to the merge queue Nov 21, 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 21, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to a conflict with the base branch Nov 21, 2025
@servo-highfive servo-highfive added S-needs-rebase There are merge conflict errors. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Nov 21, 2025
Instead of storing the `Minibrowser` egui struct in the
`RunningAppState` which is used for both headed and headless windows,
move it to the headed window implementation. This addresses a lot of
bits of the code that would need to test the existance of the
`Minibrowser` even when it should have always existed.

In addition, event handling for the headed window is moved into the
headed window implementation itself, which eliminates the need to expose
so many methods in `WindowPortsMethods`.

One downside is that it is impossible to support tracing of `AppEvent`s
as accessibilty events are not cloneable. This seems like a small price
to pay for this cleanup though.

This is the first step toward supporting multiple windows in servoshell.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
@mrobinson mrobinson force-pushed the move-minibrowser-to-headed-window branch from a428595 to 328f385 Compare November 21, 2025 13:58
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-rebase There are merge conflict errors. labels Nov 21, 2025
@mrobinson mrobinson enabled auto-merge November 21, 2025 13:59
@mrobinson mrobinson added this pull request to the merge queue Nov 21, 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 21, 2025
Merged via the queue into servo:main with commit 1ffd3ad Nov 21, 2025
35 checks passed
@mrobinson mrobinson deleted the move-minibrowser-to-headed-window branch November 21, 2025 15:24
@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 21, 2025
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.

4 participants