Skip to content

layout: Produce an empty display list when the Document element is removed#44133

Merged
Loirooriol merged 2 commits into
servo:mainfrom
rovertrack:issue-44101
Apr 18, 2026
Merged

layout: Produce an empty display list when the Document element is removed#44133
Loirooriol merged 2 commits into
servo:mainfrom
rovertrack:issue-44101

Conversation

@rovertrack

@rovertrack rovertrack commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Previously, when the Document element was removed, no further display list updates would be sent to paint. This change makes it so that when the Document element is removed a single new empty display list is sent.

Testing: This change adds a new WPT test .
Fixes: #44101

@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Apr 12, 2026
@rovertrack rovertrack requested a review from gterzian as a code owner April 12, 2026 06:31
@rovertrack

Copy link
Copy Markdown
Contributor Author

@mukilan I think this fixes iframe content not being removed!
Thanks

@mrobinson

Copy link
Copy Markdown
Member

@rovertrack A couple things:

  • The Fixes line is at the bottom of the template and it should stay at the bottom when you fill out the PR description.
  • A screenshot of a good result is not a valid test. If possible you should try to write an automated test for this fix.

@rovertrack

Copy link
Copy Markdown
Contributor Author

@rovertrack A couple things:

  • The Fixes line is at the bottom of the template and it should stay at the bottom when you fill out the PR description.
  • A screenshot of a good result is not a valid test. If possible you should try to write an automated test for this fix.

Thanks @mrobinson, will add tests for this fix

@yezhizhen yezhizhen added the T-linux-wpt Do a try run of the WPT label Apr 12, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Apr 12, 2026
@github-actions

Copy link
Copy Markdown

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

@github-actions

Copy link
Copy Markdown

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

Flaky unexpected result (35)
  • 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
      

  • CRASH [expected ERROR] /_webgl/conformance2/misc/uninitialized-test-2.html (#41656)
  • ERROR [expected CRASH] /_webgl/conformance2/textures/misc/tex-3d-size-limit.html (#42881)
    • PASS [expected FAIL] subtest: WebGL test #1
    • FAIL [expected PASS] subtest: WebGL test #3

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

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

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : texImage3D should fail for dimension out of range. expected true got false
      

    • And 27 more unexpected results...
  • CRASH [expected OK] /_webgl/conformance2/wasm/readpixels-2gb-in-4gb-wasm-memory.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • FAIL [expected PASS] /css/css-backgrounds/border-image-repeat-space-9.html
  • OK /css/css-fonts/variations/font-weight-matching.html (#38577)
    • FAIL [expected PASS] subtest: Test @font-face matching for weight 99

      assert_approx_equals: @font-face should be mapped to CSSTest Weights 900. expected 90 +/- 2 but got 180
      

    • FAIL [expected PASS] subtest: Test @font-face matching for weight 100

      assert_approx_equals: @font-face should be mapped to CSSTest Weights 900. expected 90 +/- 2 but got 180
      

    • FAIL [expected PASS] subtest: Test @font-face matching for weight 249

      assert_approx_equals: @font-face should be mapped to CSSTest Weights 900. expected 90 +/- 2 but got 180
      

    • FAIL [expected PASS] subtest: Test @font-face matching for weight 470

      assert_approx_equals: @font-face should be mapped to CSSTest Weights 300. expected 90 +/- 2 but got 180
      

    • FAIL [expected PASS] subtest: Test @font-face matching for weight 500

      assert_approx_equals: @font-face should be mapped to CSSTest Weights 300. expected 90 +/- 2 but got 180
      

  • PASS [expected FAIL] /css/css-ui/appearance-menulist-button-002.tentative.html
  • FAIL [expected PASS] /css/css-ui/webkit-appearance-menulist-001.html
  • FAIL [expected PASS] /css/css-ui/webkit-appearance-meter-001.html
  • 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
      

  • CRASH [expected OK] /fetch/api/redirect/redirect-to-dataurl.any.worker.html
  • CRASH [expected OK] /fetch/api/request/destination/fetch-destination-no-load-event.https.html
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/sandboxed-iframe.https.window.html (#41704)
  • 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
  • ERROR [expected OK] /fetch/metadata/window-open.https.sub.html (#40339)
  • 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)&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.html (#44028)
  • CRASH [expected TIMEOUT] /html/browsers/browsing-the-web/unloading-documents/prompt-and-unload-script-closeable.html
  • OK /html/browsers/history/the-history-interface/traverse_the_history_4.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

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

  • CRASH [expected OK] /html/browsers/history/the-location-interface/location-ancestor-origins-new-object.html
  • CRASH [expected TIMEOUT] /html/canvas/offscreen/text/2d.text.measure.fillTextCluster-baseline.tentative.w.html
  • TIMEOUT /html/semantics/embedded-content/media-elements/preserves-pitch.html (#40352)
    • PASS [expected TIMEOUT] subtest: Speed-ups should not change the pitch when preservesPitch=true
    • PASS [expected NOTRUN] subtest: Slow-downs should not change the pitch when preservesPitch=true
    • TIMEOUT [expected NOTRUN] subtest: Speed-ups should change the pitch when preservesPitch=false

      Test timed out
      

  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-link-click.html (#32664)
    • FAIL [expected PASS] subtest: Navigating iframe loading='lazy' before it is loaded: link click

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

  • OK /html/semantics/embedded-content/the-iframe-element/iframe-loading-lazy-nav-window-open.html (#32596)
    • FAIL [expected PASS] subtest: Navigating iframe loading='lazy' before it is loaded: location.replace

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

  • 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
      

  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • TIMEOUT [expected OK] /infrastructure/testdriver/click_nested.html (#43887)
    • NOTRUN [expected FAIL] subtest: TestDriver click method with multiple windows and nested iframe
  • PASS [expected FAIL] /png/apng/acTL-plays-one.html (#41218)
  • TIMEOUT [expected OK] /pointerevents/compat/pointerevent_touch-action_two-finger_interaction.html
    • 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 /touch-events/single-tap-when-touchend-listener-use-sync-xhr.html (#41175)
    • PASS [expected FAIL] subtest: Click event should be fired when touchend opens synchronous XHR
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • PASS [expected FAIL] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.
    • 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!\""
      

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

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Navigate a window via form-submission with javascript:-urls w/ default policy in report-only mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
    • NOTRUN [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in enforcing mode.
  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)
  • OK /xhr/send-redirect.htm (#32026)
    • FAIL [expected PASS] subtest: XMLHttpRequest: send() - Redirects (basics) (308, GET, content.py)

      assert_equals: expected (string) "GET" but got (object) null
      

Stable unexpected results that are known to be intermittent (22)
  • OK /IndexedDB/idbdatabase_deleteObjectStore.any.worker.html (#43776)
    • PASS [expected FAIL] subtest: Deleted object store's name should be removed from database's list. Attempting to use a deleted IDBObjectStore should throw an InvalidStateError
  • 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)
    • 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...
  • CRASH [expected OK] /content-security-policy/meta/sandbox-iframe.html (#43478)
  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai)
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted monospace (drawing text in a canvas)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted system-ui (drawing text in a canvas)

      assert_equals: quoted system-ui matches  @font-face rule expected 125 but got 40
      

    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(nastaliq) (drawing text in a canvas)
    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)

      assert_equals: unquoted ui-serif does not match @font-face rule expected 40 but got 125
      

    • FAIL [expected PASS] subtest: @font-face matching for quoted and unquoted ui-sans-serif (drawing text in a canvas)

      assert_equals: unquoted ui-sans-serif does not match @font-face rule expected 40 but got 125
      

  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • ERROR [expected OK] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-mode
  • TIMEOUT /fetch/metadata/generated/css-images.https.sub.tentative.html (#42229)
    • FAIL [expected PASS] subtest: content sec-fetch-site - Same-Origin -> Same Origin

      assert_unreached: Reached unreachable code
      

  • TIMEOUT [expected ERROR] /html/browsers/browsing-the-web/history-traversal/pageswap/pageswap-initial-navigation.html (#40387)
  • 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])
      

  • ERROR [expected OK] /html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html (#43710)
  • TIMEOUT /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected NOTRUN] subtest: Host element with delegatesFocus should support autofocus

      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 /mixed-content/tentative/autoupgrades/mixed-content-cors.https.sub.html (#41123)
    • PASS [expected FAIL] subtest: Cross-Origin video should get upgraded even if CORS is set
  • OK /preload/prefetch-document.html (#37210)
    • FAIL [expected PASS] subtest: different-site document prefetch with 'as=document' should not be consumed

      assert_equals: expected 2 but got 1
      

  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (iframe)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (xmlhttprequest)
  • 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.
  • OK /trusted-types/trusted-types-reporting.html (#43737)
    • PASS [expected FAIL] subtest: Trusted Type violation report: creating a report-only-forbidden policy.
Stable unexpected results (2)
  • OK /css/cssom-view/elementFromPoint.html
    • PASS [expected FAIL] subtest: no hit target at x,y
  • OK /css/cssom-view/elementsFromPoint.html
    • PASS [expected FAIL] subtest: no hit target at x,y

@github-actions

Copy link
Copy Markdown

⚠️ Try run (#24301470301) failed!

Comment on lines +698 to +703
Some(element) => element,
None => return,
None => {
// Trigger update if the document element was removed.
self.add_restyle_reason(RestyleReason::DOMChanged);
return;
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It looks like this will constantly add restyles if there is no document, but we probably only want to do this once when the document is removed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added root_removal_noted flag for better tracking and to make sure this is only done once!

Comment thread components/layout/layout_impl.rs Outdated
Comment on lines +874 to +887
// If we previously generated a display list, clear cached trees and
// send an empty transaction to clear the stale content.
if self.have_ever_generated_display_list.get() {
self.box_tree.borrow_mut().take();
self.fragment_tree.borrow_mut().take();
self.stacking_context_tree.borrow_mut().take();
self.paint_api
.send_initial_transaction(self.webview_id, self.id.into());
self.have_ever_generated_display_list.set(false);
return Some(ReflowResult {
reflow_phases_run: ReflowPhasesRun::BuiltDisplayList,
..Default::default()
});
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We likely only want to do this once when the document becomes empty. We should probably track whether the last display we sent was empty and also move this to a helper method called send_empty_display_list. Additionally, I don't think we want to reuse send_initial_transaction here as that has a special behavior.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

in the latest changes

  • Added a last_display_list_was_empty flag for tracking
  • Implemented the send_empty_display_list helper method

@servo-highfive servo-highfive added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Apr 12, 2026
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Apr 14, 2026
@rovertrack rovertrack requested a review from mrobinson April 14, 2026 13:04
@rovertrack rovertrack force-pushed the issue-44101 branch 2 times, most recently from fc7d6a0 to 1a3df8d Compare April 14, 2026 14:28
@rovertrack

Copy link
Copy Markdown
Contributor Author

@mrobinson all the suggested improvements have been made

test added ./mach test-wpt tests/wpt/mozilla/tests/dom/nodes/Document-documentElement-remove-clears-content.html

Comment thread components/layout/layout_impl.rs Outdated
Comment on lines +1312 to +1313
self.last_display_list_was_empty.set(false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's set this below where we set the other LayoutImpl flags.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in latest changes.

pending_scroll_events: Default::default(),
rendering_update_reasons: Default::default(),
waiting_on_canvas_image_updates: Cell::new(false),
root_removal_noted: Cell::new(false),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this needs to be true as there we don't need to send an empty display list when the page is first created.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in latest changes.

Comment on lines +700 to +712
let document_element = match self.GetDocumentElement() {
Some(element) => element,
None => return,
Some(element) => {
self.root_removal_noted.set(false);
element
},
None => {
// Trigger update if the document element was removed.
if !self.root_removal_noted.get() {
self.add_restyle_reason(RestyleReason::DOMChanged);
self.root_removal_noted.set(true);
}
return;
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This can just be:

let Some(document_element) = self.GetDocumentElement() else {
    // Trigger update if the document element was removed.
    if !self.root_removal_noted.get() {
        self.add_restyle_reason(RestyleReason::DOMChanged);
        self.root_removal_noted.set(true);
    }
    return;
};

// This ensures that if the document element is removed in the future, it
// will trigger a new empty display list.
self.root_removal_noted(false);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in latest changes.

Comment thread components/layout/layout_impl.rs Outdated
Comment on lines +1387 to +1389
/// Clear all cached layout trees
/// Send empty display list to inform the compositor to clear its display list.
fn send_empty_display_list(&self) -> Option<ReflowResult> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
/// Clear all cached layout trees
/// Send empty display list to inform the compositor to clear its display list.
fn send_empty_display_list(&self) -> Option<ReflowResult> {
/// Clear all cached layout trees and send an empty display list to paint.
fn clear_layout_trees_and_send_empty_display_list(&self) -> Option<ReflowResult> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment thread components/layout/layout_impl.rs Outdated
Comment on lines +1390 to +1407
let paint_info = self
.stacking_context_tree
.borrow()
.as_ref()
.map(|tree| tree.paint_info.clone())?;
self.box_tree.borrow_mut().take();
self.fragment_tree.borrow_mut().take();
self.stacking_context_tree.borrow_mut().take();
let mut builder = webrender_api::DisplayListBuilder::new(paint_info.pipeline_id);
builder.begin();
let (_, empty_display_list) = builder.end();
self.paint_api
.send_display_list(self.webview_id, &paint_info, empty_display_list);
self.last_display_list_was_empty.set(true);
Some(ReflowResult {
reflow_phases_run: ReflowPhasesRun::BuiltDisplayList,
..Default::default()
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's no interlace logic for one step with another. You also should create a new paint info instead of using the old one:

Suggested change
let paint_info = self
.stacking_context_tree
.borrow()
.as_ref()
.map(|tree| tree.paint_info.clone())?;
self.box_tree.borrow_mut().take();
self.fragment_tree.borrow_mut().take();
self.stacking_context_tree.borrow_mut().take();
let mut builder = webrender_api::DisplayListBuilder::new(paint_info.pipeline_id);
builder.begin();
let (_, empty_display_list) = builder.end();
self.paint_api
.send_display_list(self.webview_id, &paint_info, empty_display_list);
self.last_display_list_was_empty.set(true);
Some(ReflowResult {
reflow_phases_run: ReflowPhasesRun::BuiltDisplayList,
..Default::default()
})
// Clear layout trees.
self.box_tree.borrow_mut().take();
self.fragment_tree.borrow_mut().take();
self.stacking_context_tree.borrow_mut().take();
// Send empty display list.
let paint_info = PaintDisplayListInfo::new(
viewport_details,
Size2D::zero(),
self.id.into(),
reflow_request.epoch,
AxesScrollSensitivity { x: ScrollType::InputEvents | ScrollType::Script, y: ScrollType::InputEvents | ScrollType::Script },
!self.have_ever_generated_display_list.get(),
);
let mut builder = webrender_api::DisplayListBuilder::new(paint_info.pipeline_id);
builder.begin();
let (_, empty_display_list) = builder.end();
self.paint_api
.send_display_list(self.webview_id, &paint_info, empty_display_list);
self.last_display_list_was_empty.set(true);
self.set_have_ever_generated_display_list.set(true);
Some(ReflowResult {
reflow_phases_run: ReflowPhasesRun::BuiltDisplayList,
..Default::default()
})

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I didn't like the idea of reallocating a separate paint api guess we still should do it anyways 😅

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The epoch of the previous one is incorrect and I don't know if we can absolutely guarantee that a previous one exists. Consider the case where you immediately remove the Document element before the pipeline has had a chance to create the stacking context tree. This kind of assumption makes me nervous and I think we should write the code defensively here. In the end, the entire data structure is serialized or copied onto the channel anyway.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Great 👍 will implement it
Thanks

@servo-highfive servo-highfive added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Apr 15, 2026
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Apr 15, 2026
@servo-highfive servo-highfive added S-tests-failed The changes caused existing tests to fail. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Apr 18, 2026
@mukilan

mukilan commented Apr 18, 2026

Copy link
Copy Markdown
Member

There are two new subtests in /css/cssom-view/elementFromPoint.html that started passing because of this change. The test removes the documentElement and attempts an hit-test to ensure it returns null and we were failing this before because we were still rendering the documentElement.

@rovertrack: You'll need to update the test expectations for these two subtests. It is documented in the book here.

@mukilan

mukilan commented Apr 18, 2026

Copy link
Copy Markdown
Member

I've updated the PR description. @rovertrack, in the future please ensure that the Testing field accurately reflects what is happening in the PR. I believe it was just a copy-and-pasted description from another PR that didn't reflect this one.

Really sorry for that ,I got confused! Very thank you @mrobinson

@mrobinson : I think you actually added the previous Testing: field when you updated the original description. It doesn't seem like it was a copy-paste but it was just calling out the two new sub tests that started passing with the original version of this change.

@rovertrack

Copy link
Copy Markdown
Contributor Author

There are two new subtests in /css/cssom-view/elementFromPoint.html that started passing because of this change. The test removes the documentElement and attempts an hit-test to ensure it returns null and we were failing this before because we were still rendering the documentElement.

@rovertrack: You'll need to update the test expectations for these two subtests. It is documented in the book here.

@mukilan so was this the reason this is not merged ??

@Loirooriol

Copy link
Copy Markdown
Contributor

Yes

Signed-off-by: Rover track <rishan.pgowda@gmail.com>
Signed-off-by: Rover track <rishan.pgowda@gmail.com>
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-tests-failed The changes caused existing tests to fail. labels Apr 18, 2026
@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

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

@mrobinson

Copy link
Copy Markdown
Member

@mrobinson : I think you actually added the previous Testing: field when you updated the original description. It doesn't seem like it was a copy-paste but it was just calling out the two new sub tests that started passing with the original version of this change.

Oh, you are probably right @mukilan. Sorry, @rovertrack, it was my fault all along. 🤦

@rovertrack

Copy link
Copy Markdown
Contributor Author

@mrobinson : I think you actually added the previous Testing: field when you updated the original description. It doesn't seem like it was a copy-paste but it was just calling out the two new sub tests that started passing with the original version of this change.

Oh, you are probably right @mukilan. Sorry, @rovertrack, it was my fault all along. 🤦

@mrobinson please that happens to all of us ,i have messed up things more than you in this PR 😅

@rovertrack rovertrack requested review from mrobinson and mukilan April 18, 2026 15:12
@rovertrack

Copy link
Copy Markdown
Contributor Author

@mukilan thanks for the instructions I have updated the tests regarding the expectation of the subtests which you had referred

@servo-wpt-sync

Copy link
Copy Markdown
Collaborator

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

@mukilan mukilan added this pull request to the merge queue Apr 18, 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 Apr 18, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Apr 18, 2026
@servo-highfive servo-highfive added S-tests-failed The changes caused existing tests to fail. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Apr 18, 2026
@Loirooriol Loirooriol added this pull request to the merge queue Apr 18, 2026
@servo-highfive servo-highfive added S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. and removed S-tests-failed The changes caused existing tests to fail. labels Apr 18, 2026
Merged via the queue into servo:main with commit 8dfb6be Apr 18, 2026
40 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 Apr 18, 2026
@rovertrack

Copy link
Copy Markdown
Contributor Author

Thanks to everyone for their support on this PR 🙂!

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.

Content still displayed after deleting document.documentElement

7 participants