From 56102e0297080ce4db0841e6293b50dbfe21e30b Mon Sep 17 00:00:00 2001 From: Jason Finch Date: Fri, 27 Mar 2026 22:48:35 +1000 Subject: [PATCH] Fix CSS inherit keyword not resolving on shorthand properties. The `inherit` keyword on shorthand properties like `border`, `margin`, and `padding` was not correctly resolved to parent values. Three independent bugs contributed: 1. DeclarationInfo: shorthand converters with null InitialValue were not wrapped with StandardValueConverter, so CSS-wide keywords like `inherit` were either ignored or partially mis-parsed by the property-specific converter. Fixed by always wrapping with StandardValueConverter first. 2. DeclarationInfoExtensions.Expand: CssInheritValue and CssUnsetValue were not handled when expanding shorthand properties to longhands, causing them to fall through to the aggregator Split which returned null. Added explicit handling for both value types. 3. DeclarationInfoExtensions.Collapse: CssInheritValue was not tracked for round-trip serialization of shorthand properties. Added inherit tracking alongside initial, unset, and child. 4. CssStyleDeclaration.ChangeDeclarations: when UpdateDeclarations found a child property with explicit `inherit` value, it removed it but did not allow the parent value to replace it because the skip flag was not reset. Fixed by setting skip to false when the old declaration is removed. --- .../Declarations/CssBorderProperty.cs | 125 ++++++++++++++++++ .../Library/ComputedStyle.cs | 79 +++++++++++ src/AngleSharp.Css/DeclarationInfo.cs | 2 +- .../Dom/Internal/CssStyleDeclaration.cs | 1 + .../Extensions/DeclarationInfoExtensions.cs | 19 +++ 5 files changed, 225 insertions(+), 1 deletion(-) diff --git a/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs b/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs index 98f667e5..535bf077 100644 --- a/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs +++ b/src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs @@ -586,5 +586,130 @@ public void CssBorderAggregation() style.SetBorderColor("black"); Assert.AreEqual(expectedCss, style.CssText); } + + [Test] + public void CssBorderInheritShouldResolveToParentValues() + { + var source = @" + + + +
Cell
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var td = document.QuerySelector("td"); + var style = td.ComputeCurrentStyle(); + Assert.AreEqual("solid", style.GetBorderTopStyle()); + Assert.AreEqual("3px", style.GetBorderTopWidth()); + } + + [Test] + public void CssBorderInheritShouldResolveAllSides() + { + var source = @" + + + +
Content
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("dashed", style.GetBorderTopStyle()); + Assert.AreEqual("dashed", style.GetBorderBottomStyle()); + Assert.AreEqual("dashed", style.GetBorderLeftStyle()); + Assert.AreEqual("dashed", style.GetBorderRightStyle()); + Assert.AreEqual("2px", style.GetBorderTopWidth()); + Assert.AreEqual("2px", style.GetBorderBottomWidth()); + } + + [Test] + public void CssBorderStyleInheritShouldResolveToParentValues() + { + var source = @" + + + +
Content
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("dotted", style.GetBorderTopStyle()); + Assert.AreEqual("dotted", style.GetBorderRightStyle()); + } + + [Test] + public void CssBorderWidthInheritShouldResolveToParentValues() + { + var source = @" + + + +
Content
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("5px", style.GetBorderTopWidth()); + Assert.AreEqual("5px", style.GetBorderLeftWidth()); + } + + [Test] + public void CssBorderInheritThroughMultipleLevels() + { + var source = @" + + + +
Deep
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var inner = document.QuerySelector("div.inner"); + var style = inner.ComputeCurrentStyle(); + Assert.AreEqual("solid", style.GetBorderTopStyle()); + Assert.AreEqual("4px", style.GetBorderTopWidth()); + } + + [Test] + public void CssBorderInheritWithExplicitChildOverride() + { + var source = @" + + + +
Content
+ +"; + var document = source.ToHtmlDocument(Configuration.Default.WithCss()); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("dotted", style.GetBorderTopStyle()); + Assert.AreEqual("solid", style.GetBorderBottomStyle()); + Assert.AreEqual("3px", style.GetBorderTopWidth()); + } } } diff --git a/src/AngleSharp.Css.Tests/Library/ComputedStyle.cs b/src/AngleSharp.Css.Tests/Library/ComputedStyle.cs index 6ee3f880..3d5bb058 100644 --- a/src/AngleSharp.Css.Tests/Library/ComputedStyle.cs +++ b/src/AngleSharp.Css.Tests/Library/ComputedStyle.cs @@ -1,9 +1,11 @@ namespace AngleSharp.Css.Tests.Library { + using AngleSharp.Css.Dom; using AngleSharp.Dom; using AngleSharp.Html.Dom; using NUnit.Framework; using System.Threading.Tasks; + using static CssConstructionFunctions; [TestFixture] public class ComputedStyleTests @@ -25,5 +27,82 @@ public async Task TransformEmToPx_Issue136() Assert.AreEqual("24px", fontSize.Value); } + + [Test] + public void MarginInheritShouldResolveToParentValues() + { + var source = @" + + + +
Content
+ +"; + var document = ParseDocument(source); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("10px", style.GetMarginTop()); + Assert.AreEqual("20px", style.GetMarginRight()); + Assert.AreEqual("10px", style.GetMarginBottom()); + Assert.AreEqual("20px", style.GetMarginLeft()); + } + + [Test] + public void PaddingInheritShouldResolveToParentValues() + { + var source = @" + + + +
Content
+ +"; + var document = ParseDocument(source); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("5px", style.GetPaddingTop()); + Assert.AreEqual("15px", style.GetPaddingRight()); + Assert.AreEqual("10px", style.GetPaddingBottom()); + Assert.AreEqual("15px", style.GetPaddingLeft()); + } + + [Test] + public void InheritOnNonInheritablePropertyShouldResolveFromParent() + { + var source = @" + + + +
Content
+ +"; + var document = ParseDocument(source); + var child = document.QuerySelector("div.child"); + var style = child.ComputeCurrentStyle(); + Assert.AreEqual("solid", style.GetBorderTopStyle()); + Assert.AreEqual("1px", style.GetBorderTopWidth()); + Assert.AreEqual("8px", style.GetPaddingTop()); + } + + [Test] + public void BorderInheritShouldSerializeCorrectly() + { + var html = @""; + var dom = ParseDocument(html); + var styleSheet = dom.StyleSheets[0] as ICssStyleSheet; + var rule = styleSheet.Rules[0] as ICssStyleRule; + Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-style")); + Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-width")); + Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-color")); + } } } diff --git a/src/AngleSharp.Css/DeclarationInfo.cs b/src/AngleSharp.Css/DeclarationInfo.cs index 9391ac78..d12f6f6c 100644 --- a/src/AngleSharp.Css/DeclarationInfo.cs +++ b/src/AngleSharp.Css/DeclarationInfo.cs @@ -21,7 +21,7 @@ public class DeclarationInfo public DeclarationInfo(String name, IValueConverter converter, PropertyFlags flags = PropertyFlags.None, ICssValue initialValue = null, String[] shorthands = null, String[] longhands = null) { Name = name; - Converter = initialValue != null ? Or(converter, AssignInitial(initialValue)) : converter; + Converter = initialValue != null || longhands?.Length > 0 ? Or(AssignInitial(initialValue), converter) : converter; Aggregator = converter as IValueAggregator; Flags = flags; InitialValue = initialValue; diff --git a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs index 11f937ee..79244361 100644 --- a/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs +++ b/src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs @@ -397,6 +397,7 @@ private void ChangeDeclarations(IEnumerable decls, Predicate() .ToArray(); } + else if (value is CssInheritValue) + { + return Enumerable + .Repeat(value, longhands.Length) + .ToArray(); + } + else if (value is CssUnsetValue) + { + return longhands + .Select(name => new CssUnsetValue(factory.Create(name)?.InitialValue)) + .OfType() + .ToArray(); + } return info.Aggregator?.Split(value); }