Skip to content

script: More carefully handle dirty root and descendant damage marking - #46906

Merged
mrobinson merged 1 commit into
servo:mainfrom
mrobinson:dirty-root
Aug 4, 2026
Merged

mrobinson merged 1 commit into
servo:mainfrom
mrobinson:dirty-root

Conversation

@mrobinson

@mrobinson mrobinson commented Jul 31, 2026 •

Copy link
Copy Markdown
Member

Script passes a dirty root to layout along with setting the
HAS_DIRTY_DESCENDANTS flag on elements at or below that root indicating
that children need to be traversed. There was an undocumented invariant
that no nodes above that dirty root were marked with
HAS_DIRTY_DESCENDANTS, as the flag itself was used to determine
when the dirty root is raised to some common ancestor of the old dirty
root and a damaged node.

There were some problems with the old implementation and this change
fixes them by hewing closer to Gecko (from which we took this design).
Notably:

  • In the complex situation where we cannot trivially calculate the dirty
    root, we now calculate it by walking up from the old dirty root and
    finding the first element which already has the flag set.
  • When removing nodes from the flat tree, if they were the previous
    dirty root, the dirty root is cleared.
  • Ensure that all marking traversals for the HAS_DIRTY_DESCENDANTS flag
    marking happen in the flat tree.
  • When slotting nodes into <slot> elements, the style and layout data
    of fallback content is cleared, and the dirty root is potentially reset.
  • Comments are added everywhere in this tricky algorithm explaining what
    the heck is going on.
  • The new debug assertion for the invariant found a case where we were
    triggering restyles too late before a relayout -- when setting selection flags.
    This is moved earlier in the method.

Testing: This change adds a new WPT crash test.
Fixes: #46024.
Fixes: #46883.

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

🤖 Opened new upstream WPT pull request (web-platform-tests/wpt#61656) with upstreamable changes.

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#61656).

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

✍ Updated existing upstream WPT pull request (web-platform-tests/wpt#61656) title and body.

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

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (40)
  • TIMEOUT [expected OK] /_mozilla/input-events/sequential-focus-navigation-non-sequentially-focusable-starting-point-001.html
  • TIMEOUT [expected OK] /_mozilla/mozilla/iframe-contentDocument.html
  • TIMEOUT [expected OK] /_webgl/conformance/reading/read-pixels-test.html
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-06.html
  • TIMEOUT [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-16.html
    • NOTRUN [expected PASS] subtest: Overall test
  • OK /content-security-policy/inside-worker/sharedworker-connect-src.sub.html
    • FAIL [expected PASS] subtest: Reports match in http: with connect-src 'self'

      assert_array_equals: Reports do not match lengths differ, expected array ["http://web-platform.test:8000/common/redirect-opt-in.py?status=307&amp;location=https://www.web-platform.test:8444/content-security-policy/support/resource.py?cross-origin-fetch", "https://web-platform.test:8889/echo", "https://www.web-platform.test:8444/content-security-policy/support/resource.py?cross-origin-fetch", "https://www.web-platform.test:8444/content-security-policy/support/resource.py?cross-origin-xhr"] length 4, got [] length 0
      

  • OK /cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html (#45255)
    • FAIL [expected PASS] subtest: In embedded cross-site contexts, partitioned cookies can only be set with explicit SameSite=None

      assert_equals: expected 0 but got 3
      

  • FAIL [expected PASS] /css/css-backgrounds/background-size-041.html
  • FAIL [expected PASS] /css/css-sizing/dynamic-available-size-iframe.html (#45763)
  • TIMEOUT [expected CRASH] /editing/whitespaces/chrome-compat/inserttext.tentative.html
  • TIMEOUT [expected OK] /fetch/content-encoding/br/big-br-body.https.any.html
  • TIMEOUT [expected OK] /fetch/content-encoding/br/big-br-body.https.any.worker.html
    • TIMEOUT [expected FAIL] subtest: large br data should be decompressed successfully with byte stream

      Test timed out
      

  • TIMEOUT [expected OK] /fetch/content-encoding/gzip/big-gzip-body.https.any.sharedworker.html
    • TIMEOUT [expected PASS] subtest: large gzip data should be decompressed successfully

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large gzip data should be decompressed successfully with byte stream
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • TIMEOUT /fetch/metadata/generated/css-images.https.sub.tentative.html (#42229)
    • FAIL [expected PASS] subtest: content sec-fetch-site - Cross-Site -&gt; Same-Site

      assert_unreached: Reached unreachable code
      

    • FAIL [expected PASS] subtest: content sec-fetch-site - Cross-Site -&gt; Cross-Site

      assert_unreached: Reached unreachable code
      

    • FAIL [expected PASS] subtest: content sec-fetch-site - Same-Origin -&gt; Same-Site

      assert_unreached: Reached unreachable code
      

    • FAIL [expected PASS] subtest: content sec-fetch-site - Same-Site -&gt; Same-Site

      assert_unreached: Reached unreachable code
      

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

      assert_unreached: Reached unreachable code
      

  • TIMEOUT /fetch/metadata/generated/css-images.sub.tentative.html (#29047)
    • FAIL [expected PASS] subtest: content sec-fetch-site - HTTPS downgrade-upgrade

      assert_unreached: Reached unreachable code
      

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

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement=" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20form%20%3D%20document.createElement(%22form%22)%3B%0A%20%20%20%20form.action%20%3D%20%22%2Fcommon%2Fblank.html%22%3B%0A%0A%20%20%20%20const%20input%20%3D%20document.createElement(%22input%22)%3B%0A%20%20%20%20input.type%20%3D%20%22hidden%22%3B%0A%20%20%20%20input.name%20%3D%20%22thereplacement%22%3B%0A%20%20%20%20form.append(input)%3B%0A%0A%20%20%20%20document.currentScript.before(form)%3B%0A%20%20%20%20form.requestSubmit()%3B%0A%20%20"
      

  • OK [expected TIMEOUT] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit-button-click.html (#44099)
    • FAIL [expected TIMEOUT] subtest: Replace before load, triggered by submitButton.click()

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement=" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20form%20%3D%20document.createElement(%22form%22)%3B%0A%20%20%20%20form.action%20%3D%20%22%2Fcommon%2Fblank.html%22%3B%0A%0A%20%20%20%20const%20input%20%3D%20document.createElement(%22input%22)%3B%0A%20%20%20%20input.type%20%3D%20%22hidden%22%3B%0A%20%20%20%20input.name%20%3D%20%22thereplacement%22%3B%0A%20%20%20%20form.append(input)%3B%0A%0A%20%20%20%20const%20button%20%3D%20document.createElement(%22button%22)%3B%0A%20%20%20%20button.type%20%3D%20%22submit%22%3B%0A%20%20%20%20form.append(button)%3B%0A%0A%20%20%20%20document.currentScript.before(form)%3B%0A%20%20%20%20button.click()%3B%0A%20%20"
      

  • TIMEOUT /html/browsers/history/the-history-interface/001.html (#12580)
    • PASS [expected FAIL] subtest: traversing history must also traverse hash changes
  • TIMEOUT [expected ERROR] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • OK /html/browsers/windows/browsing-context-names/duplicate-name-order.html (#34623)
    • PASS [expected FAIL] subtest: Duplicate name lookup order
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/document-with-fragment-empty.html (#28259)
    • TIMEOUT [expected FAIL] subtest: Autofocus elements in top-level browsing context's documents with empty fragments should work.

      Test timed out
      

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

      Test timed out
      

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

      assert_greater_than: seekable ranges expected a number greater than 0 but got 0
      

  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-reload-location-reload.html (#32595)
    • FAIL [expected PASS] subtest: Reloading iframe loading='lazy' before it is loaded: location.reload

      uncaught exception: Error: assert_equals: expected "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?src" but got "about:blank"
      

  • 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 FAIL] /html/semantics/interestfor/interestfor-plain-inline-element.tentative.html
  • TIMEOUT [expected OK] /html/syntax/parsing/html5lib_write_single.html?file=tests19
    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;math&gt;&lt;mn DefinitionUrl="foo"&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;html&gt;&lt;/p&gt;&lt;!--foo--&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;head&gt;&lt;/head&gt;&lt;/p&gt;&lt;!--foo--&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;body&gt;&lt;p&gt;&lt;pre&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;body&gt;&lt;p&gt;&lt;listing&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;p&gt;&lt;plaintext&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;p&gt;&lt;h1&gt;

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: &lt;!doctype html&gt;&lt;isindex type="hidden"&gt;

      Test timed out
      

    • NOTRUN [expected PASS] subtest: &lt;!doctype html&gt;&lt;ruby&gt;&lt;p&gt;&lt;rp&gt;
    • NOTRUN [expected PASS] subtest: &lt;!doctype html&gt;&lt;ruby&gt;&lt;div&gt;&lt;span&gt;&lt;rp&gt;
    • And 93 more unexpected results...
  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html (#45151)
    • TIMEOUT [expected FAIL] subtest: Post-navigation state reset.

      Test timed out
      

  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html (#44693)
    • NOTRUN [expected PASS] subtest: touch two-finger pan on 'touch-action: pan-x pan-y'
    • NOTRUN [expected FAIL] subtest: touch two-finger pan on 'touch-action: pinch-zoom'
  • OK /pointerevents/pointerevent_pointercapture_in_frame.html?touch (#45445)
    • FAIL [expected PASS] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture.

      assert_true: Timed out waiting for lostpointercapture expected true got false
      

    • FAIL [expected PASS] subtest: Test touchpointer capture in same-origin frame: Pointer down at inner frame and set pointer capture to outer frame should not capture.

      assert_array_equals: Received events: innerFrame received pointerdown,innerFrame received pointermove,innerFrame received pointerup,innerFrame received lostpointercapture lengths differ, expected array ["innerFrame received pointerdown", "innerFrame received pointermove", "innerFrame received pointerup"] length 3, got ["innerFrame received pointerdown", "innerFrame received pointermove", "innerFrame received pointerup", "innerFrame received lostpointercapture"] length 4
      

  • 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
  • CRASH [expected OK] /trusted-types/TrustedTypePolicyFactory-createPolicy-unenforced.html
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • TIMEOUT [expected FAIL] subtest: Navigate a frame via area with javascript:-urls in report-only mode.

      Test timed out
      

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

  • OK /upgrade-insecure-requests/gen/sharedworker-classic-data.meta/upgrade/websocket.https.html
    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for websocket to cross-ws-downgrade origin and no-redirect redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for websocket to same-ws-downgrade origin and no-redirect redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

  • OK /visual-viewport/resize-event-order.html (#41981)
    • PASS [expected FAIL] subtest: Popup: DOMWindow resize fired before VisualViewport.
  • CRASH [expected OK] /webstorage/storage_session_setitem_quotaexceedederr.window.html
Stable unexpected results that are known to be intermittent (13)
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

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

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

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

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

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

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

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

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

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

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

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

    • And 14 more unexpected results...
  • OK /beacon/beacon-basic.https.window.html (#41723)
    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = arraybuffer
  • 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."
      

  • TIMEOUT [expected OK] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html (#44028)
  • OK [expected TIMEOUT] /html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.html (#30970)
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected PASS] subtest: Non-HTMLElement should not support autofocus

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus should support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus including no focusable descendants should be skipped
    • NOTRUN [expected FAIL] subtest: Area element should support autofocus
  • TIMEOUT [expected OK] /html/semantics/embedded-content/media-elements/autoplay-disabled-by-permissions-policy.https.sub.html (#46012, #46804)
    • TIMEOUT [expected PASS] subtest: Permissions-Policy header: autoplay "none" has no effect on the top level document.

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: Permissions-Policy header: autoplay "none" disallows same-origin iframes.

      Test timed out
      

  • OK [expected TIMEOUT] /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
  • TIMEOUT [expected OK] /html/syntax/parsing/html5lib_write_single.html?file=processing-instructions (#46206)
    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?xla-&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?xla- data&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?a-b-c&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?a-b-c data&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?v-123&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?S_S-S_S data&gt;

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?b_&gt;

      Test timed out
      

  • OK [expected TIMEOUT] /infrastructure/testdriver/click_nested.html (#43887)
    • FAIL [expected NOTRUN] subtest: TestDriver click method with multiple windows and nested iframe

      can't access property "document", child.frames[2] is undefined
      

  • OK /pointerevents/pointerevent_change-touch-action-onpointerdown_touch.html (#46781)
    • FAIL [expected PASS] subtest: scroll was received while shouldn't

      assert_true: scroll received while shouldn't expected true got false
      

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

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

    • PASS [expected FAIL] subtest: Test whether setHTMLUnsafe loads the image.
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • FAIL [expected PASS] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

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

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

      Test timed out
      

@github-actions

Copy link
Copy Markdown

⚠️ Try run (#30622224415) failed!

@mrobinson

Copy link
Copy Markdown
Member Author

I've filed #46912 for the failure which is almost certainly unrelated.

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

Copy link
Copy Markdown

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

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#61656).

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

✍ Updated existing upstream WPT pull request (web-platform-tests/wpt#61656) title and body.

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (30)
  • TIMEOUT [expected OK] /IndexedDB/idbcursor-continuePrimaryKey-exception-order.any.sharedworker.html
    • TIMEOUT [expected FAIL] subtest: DataError(keys are lower then current one) in 'next' direction

      Test timed out
      

  • TIMEOUT [expected OK] /IndexedDB/idbcursor_advance_objectstore.any.worker.html
    • TIMEOUT [expected FAIL] subtest: object store - iterate cursor number of times specified by count

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Calling advance() with count argument 0 should throw TypeError.

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: Calling advance() should throws an exception TransactionInactiveError when the transaction is not active

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: Calling advance() should throw DOMException when the cursor is currently being iterated.

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: If the cursor's source or effective object store has been deleted, the implementation MUST throw a DOMException of type InvalidStateError

      Test timed out
      

  • TIMEOUT [expected OK] /IndexedDB/idbfactory_open.any.worker.html
    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - request has no source

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - database 'name' and 'version' are correctly set

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - no version opens current database

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - new database has default version

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - new database is empty

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - open database with a lower version than current

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - open database with a higher version than current

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: IDBFactory.open() - error in version change transaction aborts open

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Calling open() with version argument 1.5 should not throw.

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Calling open() with version argument 9007199254740991 should not throw.

      Test timed out
      

    • And 4 more unexpected results...
  • TIMEOUT [expected OK] /IndexedDB/transaction-deactivation-timing.any.sharedworker.html
    • TIMEOUT [expected PASS] subtest: New transactions are deactivated before next task

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: New transactions from microtask are still active through the microtask checkpoint

      Test timed out
      

    • TIMEOUT [expected PASS] subtest: Deactivation of new transactions happens at end of invocation

      Test timed out
      

  • TIMEOUT [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-06.html
    • NOTRUN [expected PASS] subtest: Overall test
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-07.html
  • CRASH [expected OK] /_webgl/conformance/uniforms/no-over-optimization-on-uniform-array-16.html
  • OK /cookies/partitioned-cookies/partitioned-cookies-samesite-attribute.https.html (#45255)
    • FAIL [expected PASS] subtest: In embedded cross-site contexts, partitioned cookies can only be set with explicit SameSite=None

      assert_equals: expected 0 but got 3
      

  • OK [expected TIMEOUT] /credential-management/credentialscontainer-frame-basics.https.html (#39430)
    • FAIL [expected TIMEOUT] subtest: navigator.credentials should be undefined in documents generated from data: URLs.

      assert_equals: expected "undefined" but got "[object CredentialsContainer]"
      

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

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: large gzip data should be decompressed successfully with byte stream
  • TIMEOUT [expected OK] /fetch/content-encoding/zstd/big-zstd-body.https.any.html
  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • FAIL [expected PASS] subtest: aElement.click() before the load event must NOT replace

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20a%20%3D%20document.createElement(%22a%22)%3B%0A%20%20%20%20a.href%20%3D%20%22%2Fcommon%2Fblank.html%3Fthereplacement%22%3B%0A%20%20%20%20document.currentScript.before(a)%3B%0A%20%20%20%20a.click()%3B%0A%20%20"
      

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

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement=" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20form%20%3D%20document.createElement(%22form%22)%3B%0A%20%20%20%20form.action%20%3D%20%22%2Fcommon%2Fblank.html%22%3B%0A%0A%20%20%20%20const%20input%20%3D%20document.createElement(%22input%22)%3B%0A%20%20%20%20input.type%20%3D%20%22hidden%22%3B%0A%20%20%20%20input.name%20%3D%20%22thereplacement%22%3B%0A%20%20%20%20form.append(input)%3B%0A%0A%20%20%20%20document.currentScript.before(form)%3B%0A%20%20%20%20form.requestSubmit()%3B%0A%20%20"
      

  • TIMEOUT /html/browsers/history/the-history-interface/001.html (#12580)
    • PASS [expected FAIL] subtest: traversing history must also traverse hash changes
  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-reload-location-reload.html (#32595)
    • FAIL [expected PASS] subtest: Reloading iframe loading='lazy' before it is loaded: location.reload

      uncaught exception: Error: assert_equals: expected "http://web-platform.test:8000/html/semantics/embedded-content/the-iframe-element/support/blank.htm?src" but got "about:blank"
      

  • 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 /paint-timing/fcp-only/fcp-svg.html (#45878)
    • PASS [expected FAIL] subtest: First contentful paint fires when SVG becomes contentful.
  • ERROR [expected TIMEOUT] /performance-timeline/not-restored-reasons/abort-block-bfcache.window.html (#42802)
  • OK /pointerevents/compat/pointerevent_touch_target_after_pointerdown_target_removed.tentative.html (#42813)
    • FAIL [expected PASS] subtest: After a pointerdown listener removes its target, touch events should be fired on the touchstart target even though an orphan and pointer events should be fired on the parent

      assert_equals: expected "[ { type: pointerdown, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: touchstart, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: pointerup, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchend, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; } ]" but got "[ { type: pointerdown, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: touchstart, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: pointerup, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchend, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: pointerdown, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; } ]"
      

    • FAIL [expected PASS] subtest: After a pointerdown listener removes its target, touchmove event should be fired on the pointerdown target

      assert_equals: expected "[ { type: pointerdown, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: touchstart, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchmove, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointerup, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchend, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; } ]" but got "[ { type: pointerdown, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: touchstart, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchmove, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointerup, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: touchend, target: Element node &lt;div id=\"pointerDownTarget\"&gt;&lt;/div&gt; }, { type: pointermove, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; }, { type: pointerdown, target: Element node &lt;div id=\"pointerDownTargetParent\"&gt;&lt;/div&gt; } ]"
      

    • PASS [expected FAIL] subtest: After a touchstart listener removes its target, touch events should be fired on the touchstart target even though an orphan and pointer events should be fired on the parent
    • PASS [expected FAIL] subtest: After a touchstart listener removes its target, touchmove event should be fired on the pointerdown target
  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (iframe)
  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • FAIL [expected PASS] subtest: Navigate a frame via anchor with javascript:-urls 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?11-15 (#44834)
    • PASS [expected TIMEOUT] subtest: Navigate a window via area with javascript:-urls in report-only mode.
  • TIMEOUT [expected OK] /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in enforcing mode.

      Test timed out
      

    • NOTRUN [expected FAIL] subtest: Navigate a frame via area with javascript:-urls in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via area with javascript:-urls w/ default policy in report-only mode.
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • PASS [expected TIMEOUT] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • PASS [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • CRASH [expected OK] /trusted-types/trusted-types-report-only.html
  • OK /upgrade-insecure-requests/gen/sharedworker-module-data.meta/upgrade/xhr.https.html
    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to cross-http-downgrade origin and downgrade redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to cross-http-downgrade origin and no-redirect redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to cross-https origin and downgrade redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to same-http-downgrade origin and downgrade redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to same-http-downgrade origin and no-redirect redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

    • FAIL [expected PASS] subtest: Upgrade-Insecure-Requests: Expects allowed for xhr to same-https origin and downgrade redirection from https context.

      assert_equals: The resource request should be 'allowed'. expected "allowed" but got "blocked"
      

  • CRASH [expected OK] /webstorage/storage_session_setitem_quotaexceedederr.window.html
Stable unexpected results that are known to be intermittent (22)
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

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

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

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

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

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

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

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

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

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

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

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

    • And 22 more unexpected results...
  • OK /beacon/beacon-basic.https.window.html (#41723)
    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = arraybuffer
    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = blob
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-weight: '500' should prefer '400' over '350 399'

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

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 20deg' should prefer 'oblique 10deg' over 'oblique 0deg'

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

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 0deg' should prefer 'oblique 5deg' over 'oblique 15deg 20deg'

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

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique -20deg' should prefer 'oblique 0deg' over 'oblique 30deg 60deg'

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

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique -21deg' should prefer 'oblique 0deg' over 'oblique 30deg 60deg'

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

  • FAIL [expected PASS] /css/css-text-decor/text-underline-position-from-font-variable.html (#45615)
  • OK /dom/nodes/moveBefore/iframe-document-preserve.window.html (#43152)
    • FAIL [expected PASS] subtest: moveBefore(): cross-origin iframe is preserved: remove self via replaceChildren()

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

  • 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
      

  • TIMEOUT /fetch/metadata/generated/css-images.https.sub.tentative.html (#42229)
    • PASS [expected FAIL] subtest: content sec-fetch-site - Same-Origin -&gt; Same Origin
  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/load-pageshow-events-window-open.html (#28691)
    • PASS [expected FAIL] subtest: load event does not fire on window.open('about:blank')
  • OK /html/browsers/browsing-the-web/navigating-across-documents/refresh/same-document-refresh.html (#34597)
    • PASS [expected FAIL] subtest: Same-Document Referrer from Refresh
  • TIMEOUT [expected ERROR] /html/browsers/history/the-location-interface/location_replace_session_history.html (#41896)
  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • PASS [expected FAIL] subtest: opener of discarded auxiliary browsing context
  • OK [expected TIMEOUT] /html/infrastructure/urls/base-url/document-base-url-window-initiator-is-not-opener.https.window.html (#30970)
  • OK [expected TIMEOUT] /html/interaction/focus/the-autofocus-attribute/autofocus-dialog.html (#29087)
    • PASS [expected TIMEOUT] subtest: &lt;dialog&gt;-contained autofocus element gets focused when the dialog is shown
  • TIMEOUT [expected OK] /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected FAIL] subtest: Element with tabindex should support autofocus

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Non-HTMLElement should not support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus should support autofocus
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus including no focusable descendants should be skipped
    • NOTRUN [expected FAIL] subtest: Area element should support autofocus
  • 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 /html/semantics/embedded-content/media-elements/autoplay-allowed-by-permissions-policy.https.sub.html (#46803)
    • TIMEOUT [expected PASS] subtest: Permissions-Policy header: autoplay * allows same-origin iframes.

      Test timed out
      

  • TIMEOUT [expected OK] /html/semantics/embedded-content/media-elements/autoplay-disabled-by-permissions-policy.https.sub.html (#46012, #46804)
    • TIMEOUT [expected PASS] subtest: Permissions-Policy header: autoplay "none" has no effect on the top level document.

      Test timed out
      

    • TIMEOUT [expected FAIL] subtest: Permissions-Policy header: autoplay "none" disallows same-origin iframes.

      Test timed out
      

  • TIMEOUT /html/syntax/parsing/html5lib_write.html?file=processing-instructions (#46205)
    • TIMEOUT [expected FAIL] subtest: &lt;body&gt;&lt;?v-123&gt;

      Test timed out
      

  • OK [expected TIMEOUT] /infrastructure/testdriver/click_nested.html (#43887)
    • FAIL [expected NOTRUN] subtest: TestDriver click method with multiple windows and nested iframe

      can't access property "document", child.frames[2] is undefined
      

  • OK /pointerevents/pointerevent_change-touch-action-onpointerdown_touch.html (#46781)
    • FAIL [expected PASS] subtest: scroll was received while shouldn't

      assert_true: scroll received while shouldn't expected true got false
      

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

@github-actions

Copy link
Copy Markdown

✨ Try run (#30645618583) succeeded.

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Aug 4, 2026
Script passes a dirty root to layout along with setting the
HAS_DIRTY_DESCENDANTS flag on elements at or below that root indicating
that children need to be traversed. There was an undocumented invariant
that no nodes above that dirty root were marked with
HAS_DIRTY_DESCENDANTS, as the flag itself was used to determine
when the dirty root is raised to some common ancestor of the old dirty
root and a damaged node.

There were some problems with the old implementation and this change
fixes them by hewing closer to Gecko (from which we took this design).
Notably:

- In the complex situation where we cannot trivially calculate the dirty
  root, we now calculate it by walking up from the old dirty root and
  finding the first element which already has the flag set.
- When removing nodes from the flat tree, if they were the previous
  dirty root, the dirty root is cleared.
- Ensure that all marking traversals for HAS_DIRTY_DESCENDANTS flag
  marking happen in the flat tree.
- When slotting nodes into `<slot>` elements, the style and layout data
  of fallback content is cleared, and the dirty root is potentially reset.
- Comments are added everywhere in this tricky algorithm explaining what
  the heck is going on.

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 Aug 4, 2026
@mrobinson
mrobinson enabled auto-merge August 4, 2026 13:22
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

📝 Transplanted new upstreamable changes to existing upstream WPT pull request (web-platform-tests/wpt#61656).

@mrobinson
mrobinson added this pull request to the merge queue Aug 4, 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 Aug 4, 2026
Merged via the queue into servo:main with commit 2eab094 Aug 4, 2026
36 checks passed
@mrobinson
mrobinson deleted the dirty-root branch August 4, 2026 14:22
@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 Aug 4, 2026
niyabits pushed a commit to niyabits/servo that referenced this pull request Aug 5, 2026
servo#46906)

Script passes a dirty root to layout along with setting the
HAS_DIRTY_DESCENDANTS flag on elements at or below that root indicating
that children need to be traversed. There was an undocumented invariant
that no nodes above that dirty root were marked with
HAS_DIRTY_DESCENDANTS, as the flag itself was used to determine
when the dirty root is raised to some common ancestor of the old dirty
root and a damaged node.

There were some problems with the old implementation and this change
fixes them by hewing closer to Gecko (from which we took this design).
Notably:

- In the complex situation where we cannot trivially calculate the dirty
  root, we now calculate it by walking up from the old dirty root and
  finding the first element which already has the flag set.
- When removing nodes from the flat tree, if they were the previous
  dirty root, the dirty root is cleared.
- Ensure that all marking traversals for the HAS_DIRTY_DESCENDANTS flag
  marking happen in the flat tree.
- When slotting nodes into `<slot>` elements, the style and layout data
of fallback content is cleared, and the dirty root is potentially reset.
- Comments are added everywhere in this tricky algorithm explaining what
  the heck is going on.
- The new debug assertion for the invariant found a case where we were
triggering restyles too late before a relayout -- when setting selection
flags.
  This is moved earlier in the method.

Testing: This change adds a new WPT crash test.
Fixes: servo#46024.
Fixes: servo#46883.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
RichardTjokroutomo pushed a commit to RichardTjokroutomo/servo that referenced this pull request Oct 6, 2026
servo#46906)

Script passes a dirty root to layout along with setting the
HAS_DIRTY_DESCENDANTS flag on elements at or below that root indicating
that children need to be traversed. There was an undocumented invariant
that no nodes above that dirty root were marked with
HAS_DIRTY_DESCENDANTS, as the flag itself was used to determine
when the dirty root is raised to some common ancestor of the old dirty
root and a damaged node.

There were some problems with the old implementation and this change
fixes them by hewing closer to Gecko (from which we took this design).
Notably:

- In the complex situation where we cannot trivially calculate the dirty
  root, we now calculate it by walking up from the old dirty root and
  finding the first element which already has the flag set.
- When removing nodes from the flat tree, if they were the previous
  dirty root, the dirty root is cleared.
- Ensure that all marking traversals for the HAS_DIRTY_DESCENDANTS flag
  marking happen in the flat tree.
- When slotting nodes into `<slot>` elements, the style and layout data
of fallback content is cleared, and the dirty root is potentially reset.
- Comments are added everywhere in this tricky algorithm explaining what
  the heck is going on.
- The new debug assertion for the invariant found a case where we were
triggering restyles too late before a relayout -- when setting selection
flags.
  This is moved earlier in the method.

Testing: This change adds a new WPT crash test.
Fixes: servo#46024.
Fixes: servo#46883.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
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

4 participants