ladybird/Libraries/LibWeb/CSS/CSSImportRule.h
sideshowbarker 1e0f6d1f7c LibWeb: Delay load event during CSS @import fetch
Problem: Ref/input/css/images-load-relative-to-imported-style-sheet.html
intermittently fails on the Linux Sanitizer with a deterministic 398px
screenshot mismatch — the entire 20x20 background- image div is
unpainted in the test pass, while the reference paints the checkerboard.

Cause: CSSImportRule::fetch() added to the document’s render-blocking
set (m_pending_css_import_rules) — but didn’t delay the load event. The
load event could fire before the imported stylesheet had finished
fetching — so the background image referenced from inside that sheet
hadn’t even been requested yet when test-web’s on_load_finish callback
ran wait_for_reftest_completion and proceeded to take the screenshot. By
contrast, <link rel="stylesheet"> already delays the load event for its
own duration via m_document_load_event_delayer in HTMLLinkElement —
which is why the sibling test using <link> doesn’t flake.

Fix: Hold a DocumentLoadEventDelayer in CSSImportRule for the duration
of the import fetch, paralleling HTMLLinkElement. By the time the import
rule’s delayer clears in the ScopeGuard, the imported sheet’s image
fetches have already been kicked off via SharedResourceRequest, each of
which takes over load-event delaying through its own delayer — so
there’s no race between the two delayers releasing.
2026-06-02 09:57:02 +01:00

76 lines
2.5 KiB
C++

/*
* Copyright (c) 2021, the SerenityOS developers.
* Copyright (c) 2021-2026, Sam Atkins <sam@ladybird.org>
* Copyright (c) 2022, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibWeb/CSS/CSSRule.h>
#include <LibWeb/CSS/CSSStyleSheet.h>
#include <LibWeb/CSS/URL.h>
#include <LibWeb/DOM/DocumentLoadEventDelayer.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
namespace Web::CSS {
class WEB_API CSSImportRule final
: public CSSRule
, public CSSStyleSheet::Subresource {
WEB_PLATFORM_OBJECT(CSSImportRule, CSSRule);
GC_DECLARE_ALLOCATOR(CSSImportRule);
public:
[[nodiscard]] static GC::Ref<CSSImportRule> create(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString> layer, RefPtr<Supports>, GC::Ref<MediaList>);
virtual ~CSSImportRule() override;
URL const& url() const { return m_url; }
String href() const { return m_url.url(); }
CSSStyleSheet* loaded_style_sheet() { return m_style_sheet; }
CSSStyleSheet const* loaded_style_sheet() const { return m_style_sheet; }
GC::Ref<MediaList> media() const;
CSSStyleSheet* style_sheet_for_bindings() { return m_style_sheet; }
Optional<FlyString> layer_name() const;
Optional<String> supports_text() const;
bool matches() const;
Optional<FlyString> internal_layer_name() const { return m_layer_internal; }
Optional<FlyString> internal_qualified_layer_name(Badge<StyleScope>) const;
private:
CSSImportRule(JS::Realm&, URL, GC::Ptr<DOM::Document>, Optional<FlyString>, RefPtr<Supports>, GC::Ref<MediaList>);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Cell::Visitor&) override;
virtual void dump(StringBuilder&, int indent_levels) const override;
virtual void set_parent_style_sheet(CSSStyleSheet*) override;
virtual GC::Ptr<CSSStyleSheet> parent_style_sheet_for_subresource() override { return m_parent_style_sheet; }
virtual String serialized() const override;
void fetch();
void set_style_sheet(GC::Ref<CSSStyleSheet>);
URL m_url;
GC::Ptr<DOM::Document> m_document;
Optional<FlyString> m_layer;
Optional<FlyString> m_layer_internal;
RefPtr<Supports> m_supports;
GC::Ref<MediaList> m_media;
GC::Ptr<CSSStyleSheet> m_style_sheet;
Optional<DOM::DocumentLoadEventDelayer> m_load_event_delayer;
};
template<>
inline bool CSSRule::fast_is<CSSImportRule>() const { return type() == CSSRule::Type::Import; }
}