Files
ladybird/Libraries/LibWeb/Painting/NavigableContainerViewportPaintable.cpp
Andreas Kling fd3a09a8ce LibWeb: Don't paint render-blocked iframe documents
Parent document paints could still record and paint a nested iframe's
contents even while the child document was render-blocked. That let
unstyled iframe content leak into the parent display list before the
child's blocking stylesheet had loaded, which matched the FOUC seen in
Speedometer's TodoMVC suites.

Skip painting hosted documents from NavigableContainerViewportPaintable
while they are render-blocked. Add a reftest that keeps an iframe child
render-blocked with a delayed stylesheet and forces a parent repaint;
without the fix the child's inline FAIL text becomes visible.

The new reftest passes with this change, and the existing render-
blocking requestAnimationFrame tests still pass.
2026-03-29 23:50:47 +02:00

82 lines
3.1 KiB
C++

/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Navigable.h>
#include <LibWeb/HTML/NavigableContainer.h>
#include <LibWeb/Layout/NavigableContainerViewport.h>
#include <LibWeb/Layout/Viewport.h>
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
#include <LibWeb/Painting/DisplayList.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/NavigableContainerViewportPaintable.h>
#include <LibWeb/Painting/ViewportPaintable.h>
namespace Web::Painting {
GC_DEFINE_ALLOCATOR(NavigableContainerViewportPaintable);
GC::Ref<NavigableContainerViewportPaintable> NavigableContainerViewportPaintable::create(Layout::NavigableContainerViewport const& layout_box)
{
return layout_box.heap().allocate<NavigableContainerViewportPaintable>(layout_box);
}
NavigableContainerViewportPaintable::NavigableContainerViewportPaintable(Layout::NavigableContainerViewport const& layout_box)
: PaintableBox(layout_box)
{
}
void NavigableContainerViewportPaintable::paint(DisplayListRecordingContext& context, PaintPhase phase) const
{
if (!is_visible())
return;
PaintableBox::paint(context, phase);
if (phase == PaintPhase::Foreground) {
auto absolute_rect = this->absolute_rect();
auto clip_rect = context.rounded_device_rect(absolute_rect);
ScopedCornerRadiusClip corner_clip { context, clip_rect, normalized_border_radii_data(ShrinkRadiiForBorders::Yes) };
auto const& navigable_container = this->navigable_container();
auto* hosted_document = const_cast<DOM::Document*>(navigable_container.content_document_without_origin_check());
if (!hosted_document)
return;
if (hosted_document->is_render_blocked())
return;
// NB: The hosted document's layout may have been invalidated during the parent
// document's layout (e.g., via viewport size changes in did_set_content_size).
// Ensure it is up to date before painting.
hosted_document->update_layout(DOM::UpdateLayoutReason::HostedDocumentBeforePaint);
auto const* hosted_paint_tree = hosted_document->paintable();
if (!hosted_paint_tree)
return;
context.display_list_recorder().save();
context.display_list_recorder().add_clip_rect(clip_rect.to_type<int>());
HTML::PaintConfig paint_config;
paint_config.paint_overlay = context.should_paint_overlay();
paint_config.should_show_line_box_borders = context.should_show_line_box_borders();
auto display_list = hosted_document->record_display_list(paint_config);
context.display_list_recorder().paint_nested_display_list(display_list, context.enclosing_device_rect(absolute_rect).to_type<int>());
context.display_list_recorder().restore();
if constexpr (HIGHLIGHT_FOCUSED_FRAME_DEBUG) {
if (navigable_container.content_navigable()->is_focused()) {
context.display_list_recorder().draw_rect(clip_rect.to_type<int>(), Color::Cyan);
}
}
}
}
}