From 943103869ab7ea65a124d0c974d3a90b8a19bb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Thu, 30 Jul 2026 17:52:27 -0300 Subject: [PATCH] fix(ui): preserve CSS order for deferred stylesheets Reserve stylesheet source positions when link elements are encountered instead of assigning them when asynchronous loading completes. Track the stylesheet and rule ordinals explicitly so deferred external styles retain their document order relative to later inline styles. Preserve this ordering when extracting marked stylesheet rules. Add an SS64 regression test covering heading and button styles after a deferred stylesheet finishes loading. --- .../assets/html/ss64_deferred_cascade.css | 11 +++++ include/eepp/ui/css/stylesheet.hpp | 25 ++++++++++- include/eepp/ui/uiscenenode.hpp | 11 +++-- src/eepp/ui/css/stylesheet.cpp | 37 +++++++++++----- src/eepp/ui/uiscenenode.cpp | 37 ++++++++++------ src/tests/unit_tests/uihtml_float_tests.cpp | 44 +++++++++++++++++++ 6 files changed, 136 insertions(+), 29 deletions(-) create mode 100644 bin/unit_tests/assets/html/ss64_deferred_cascade.css diff --git a/bin/unit_tests/assets/html/ss64_deferred_cascade.css b/bin/unit_tests/assets/html/ss64_deferred_cascade.css new file mode 100644 index 000000000..8fdbd76a7 --- /dev/null +++ b/bin/unit_tests/assets/html/ss64_deferred_cascade.css @@ -0,0 +1,11 @@ +h1 { + font-size: 1.5rem; +} + +li a[href="../bash/"] { + background-color: #F8F9FA; +} + +#external-css-loaded { + height: 37px; +} diff --git a/include/eepp/ui/css/stylesheet.hpp b/include/eepp/ui/css/stylesheet.hpp index 2f17015cc..6ddd978bb 100644 --- a/include/eepp/ui/css/stylesheet.hpp +++ b/include/eepp/ui/css/stylesheet.hpp @@ -11,6 +11,8 @@ namespace EE { namespace UI { namespace CSS { class EE_API StyleSheet { public: + using SourceOrder = Uint32; + StyleSheet(); void clear(); @@ -23,6 +25,10 @@ class EE_API StyleSheet { void combineStyleSheet( const StyleSheet& styleSheet ); + void combineStyleSheet( const StyleSheet& styleSheet, SourceOrder sourceOrder ); + + SourceOrder reserveSourceOrder(); + std::shared_ptr getElementStyles( UIWidget* element, const bool& applyPseudo = false ) const; @@ -78,6 +84,18 @@ class EE_API StyleSheet { StyleSheet& operator=( const StyleSheet& other ); protected: + struct SourcePosition { + SourceOrder styleSheet{ 0 }; + Uint32 rule{ 0 }; + + bool operator<( const SourcePosition& other ) const { + if ( styleSheet != other.styleSheet ) + return styleSheet < other.styleSheet; + return rule < other.rule; + } + }; + static_assert( sizeof( SourcePosition ) == sizeof( Uint64 ) ); + Uint64 mVersion{ 1 }; Uint32 mMarker{ 0 }; std::vector> mNodes; @@ -87,8 +105,8 @@ class EE_API StyleSheet { UnorderedMap mClassNodeIndex; // Candidate buckets are independent, so retain insertion order explicitly for equal // specificity. - UnorderedMap mStyleSourceOrder; - size_t mNextStyleSourceOrder{ 0 }; + UnorderedMap mStyleSourceOrder; + SourceOrder mNextStyleSourceOrder{ 0 }; MediaQueryList::vector mMediaQueryList; KeyframesDefinitionMap mKeyframesMap; using ElementDefinitionCache = UnorderedMap>; @@ -99,6 +117,9 @@ class EE_API StyleSheet { void addMediaQueryList( MediaQueryList::ptr list ); bool addStyleToNodeIndex( StyleSheetStyle* style ); + + void addStyle( std::shared_ptr node, SourceOrder sourceOrder, + Uint32 ruleOrder ); }; }}} // namespace EE::UI::CSS diff --git a/include/eepp/ui/uiscenenode.hpp b/include/eepp/ui/uiscenenode.hpp index 0d7454952..65229c3dd 100644 --- a/include/eepp/ui/uiscenenode.hpp +++ b/include/eepp/ui/uiscenenode.hpp @@ -407,9 +407,11 @@ class EE_API UISceneNode : public SceneNode { * @param forceReloadStyle If true, forces immediate style reload (default: true). * @param baseURI If the resource was loaded from an URI, pass the URI in order to solve * relative paths in CSS + * @param sourceOrder Reserved document order for a stylesheet loaded asynchronously. */ void combineStyleSheet( const CSS::StyleSheet& styleSheet, bool forceReloadStyle = true, - URI baseURI = {} ); + URI baseURI = {}, + std::optional sourceOrder = {} ); /** * @brief Combines an inline stylesheet with the existing one. @@ -421,9 +423,11 @@ class EE_API UISceneNode : public SceneNode { * @param marker Marker to associate with the new styles. * @param baseURI If the resource was loaded from an URI, pass the URI in order to solve * relative paths in CSS + * @param sourceOrder Reserved document order for a stylesheet loaded asynchronously. */ void combineStyleSheet( const std::string& inlineStyleSheet, bool forceReloadStyle = true, - const Uint32& marker = 0, URI baseURI = {} ); + const Uint32& marker = 0, URI baseURI = {}, + std::optional sourceOrder = {} ); /** * @brief Gets the reference to the current stylesheet. @@ -1161,8 +1165,9 @@ class EE_API UISceneNode : public SceneNode { * * @param uri URI to load * @param defer Defer some specific time the CSS load (0 to just load it asynchronously) + * @param sourceOrder Document order reserved when the stylesheet link was encountered. */ - void loadCSS( URI uri, std::optional