Skip to content

Commit 56102e0

Browse files
committed
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.
1 parent 366abd1 commit 56102e0

5 files changed

Lines changed: 225 additions & 1 deletion

File tree

src/AngleSharp.Css.Tests/Declarations/CssBorderProperty.cs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,5 +586,130 @@ public void CssBorderAggregation()
586586
style.SetBorderColor("black");
587587
Assert.AreEqual(expectedCss, style.CssText);
588588
}
589+
590+
[Test]
591+
public void CssBorderInheritShouldResolveToParentValues()
592+
{
593+
var source = @"<!DOCTYPE html>
594+
<html>
595+
<head><style>
596+
table { border: 3px solid; }
597+
table * { border: inherit; }
598+
</style></head>
599+
<body>
600+
<table><tr><td>Cell</td></tr></table>
601+
</body>
602+
</html>";
603+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
604+
var td = document.QuerySelector("td");
605+
var style = td.ComputeCurrentStyle();
606+
Assert.AreEqual("solid", style.GetBorderTopStyle());
607+
Assert.AreEqual("3px", style.GetBorderTopWidth());
608+
}
609+
610+
[Test]
611+
public void CssBorderInheritShouldResolveAllSides()
612+
{
613+
var source = @"<!DOCTYPE html>
614+
<html>
615+
<head><style>
616+
div.parent { border: 2px dashed; }
617+
div.child { border: inherit; }
618+
</style></head>
619+
<body>
620+
<div class='parent'><div class='child'>Content</div></div>
621+
</body>
622+
</html>";
623+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
624+
var child = document.QuerySelector("div.child");
625+
var style = child.ComputeCurrentStyle();
626+
Assert.AreEqual("dashed", style.GetBorderTopStyle());
627+
Assert.AreEqual("dashed", style.GetBorderBottomStyle());
628+
Assert.AreEqual("dashed", style.GetBorderLeftStyle());
629+
Assert.AreEqual("dashed", style.GetBorderRightStyle());
630+
Assert.AreEqual("2px", style.GetBorderTopWidth());
631+
Assert.AreEqual("2px", style.GetBorderBottomWidth());
632+
}
633+
634+
[Test]
635+
public void CssBorderStyleInheritShouldResolveToParentValues()
636+
{
637+
var source = @"<!DOCTYPE html>
638+
<html>
639+
<head><style>
640+
div.parent { border-style: dotted; }
641+
div.child { border-style: inherit; }
642+
</style></head>
643+
<body>
644+
<div class='parent'><div class='child'>Content</div></div>
645+
</body>
646+
</html>";
647+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
648+
var child = document.QuerySelector("div.child");
649+
var style = child.ComputeCurrentStyle();
650+
Assert.AreEqual("dotted", style.GetBorderTopStyle());
651+
Assert.AreEqual("dotted", style.GetBorderRightStyle());
652+
}
653+
654+
[Test]
655+
public void CssBorderWidthInheritShouldResolveToParentValues()
656+
{
657+
var source = @"<!DOCTYPE html>
658+
<html>
659+
<head><style>
660+
div.parent { border-width: 5px; }
661+
div.child { border-width: inherit; }
662+
</style></head>
663+
<body>
664+
<div class='parent'><div class='child'>Content</div></div>
665+
</body>
666+
</html>";
667+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
668+
var child = document.QuerySelector("div.child");
669+
var style = child.ComputeCurrentStyle();
670+
Assert.AreEqual("5px", style.GetBorderTopWidth());
671+
Assert.AreEqual("5px", style.GetBorderLeftWidth());
672+
}
673+
674+
[Test]
675+
public void CssBorderInheritThroughMultipleLevels()
676+
{
677+
var source = @"<!DOCTYPE html>
678+
<html>
679+
<head><style>
680+
div.root { border: 4px solid; }
681+
div.root * { border: inherit; }
682+
</style></head>
683+
<body>
684+
<div class='root'><div class='mid'><div class='inner'>Deep</div></div></div>
685+
</body>
686+
</html>";
687+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
688+
var inner = document.QuerySelector("div.inner");
689+
var style = inner.ComputeCurrentStyle();
690+
Assert.AreEqual("solid", style.GetBorderTopStyle());
691+
Assert.AreEqual("4px", style.GetBorderTopWidth());
692+
}
693+
694+
[Test]
695+
public void CssBorderInheritWithExplicitChildOverride()
696+
{
697+
var source = @"<!DOCTYPE html>
698+
<html>
699+
<head><style>
700+
div.parent { border: 3px solid; }
701+
div.child { border: inherit; border-top-style: dotted; }
702+
</style></head>
703+
<body>
704+
<div class='parent'><div class='child'>Content</div></div>
705+
</body>
706+
</html>";
707+
var document = source.ToHtmlDocument(Configuration.Default.WithCss());
708+
var child = document.QuerySelector("div.child");
709+
var style = child.ComputeCurrentStyle();
710+
Assert.AreEqual("dotted", style.GetBorderTopStyle());
711+
Assert.AreEqual("solid", style.GetBorderBottomStyle());
712+
Assert.AreEqual("3px", style.GetBorderTopWidth());
713+
}
589714
}
590715
}

src/AngleSharp.Css.Tests/Library/ComputedStyle.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
namespace AngleSharp.Css.Tests.Library
22
{
3+
using AngleSharp.Css.Dom;
34
using AngleSharp.Dom;
45
using AngleSharp.Html.Dom;
56
using NUnit.Framework;
67
using System.Threading.Tasks;
8+
using static CssConstructionFunctions;
79

810
[TestFixture]
911
public class ComputedStyleTests
@@ -25,5 +27,82 @@ public async Task TransformEmToPx_Issue136()
2527

2628
Assert.AreEqual("24px", fontSize.Value);
2729
}
30+
31+
[Test]
32+
public void MarginInheritShouldResolveToParentValues()
33+
{
34+
var source = @"<!DOCTYPE html>
35+
<html>
36+
<head><style>
37+
div.parent { margin: 10px 20px; }
38+
div.child { margin: inherit; }
39+
</style></head>
40+
<body>
41+
<div class='parent'><div class='child'>Content</div></div>
42+
</body>
43+
</html>";
44+
var document = ParseDocument(source);
45+
var child = document.QuerySelector("div.child");
46+
var style = child.ComputeCurrentStyle();
47+
Assert.AreEqual("10px", style.GetMarginTop());
48+
Assert.AreEqual("20px", style.GetMarginRight());
49+
Assert.AreEqual("10px", style.GetMarginBottom());
50+
Assert.AreEqual("20px", style.GetMarginLeft());
51+
}
52+
53+
[Test]
54+
public void PaddingInheritShouldResolveToParentValues()
55+
{
56+
var source = @"<!DOCTYPE html>
57+
<html>
58+
<head><style>
59+
div.parent { padding: 5px 15px 10px; }
60+
div.child { padding: inherit; }
61+
</style></head>
62+
<body>
63+
<div class='parent'><div class='child'>Content</div></div>
64+
</body>
65+
</html>";
66+
var document = ParseDocument(source);
67+
var child = document.QuerySelector("div.child");
68+
var style = child.ComputeCurrentStyle();
69+
Assert.AreEqual("5px", style.GetPaddingTop());
70+
Assert.AreEqual("15px", style.GetPaddingRight());
71+
Assert.AreEqual("10px", style.GetPaddingBottom());
72+
Assert.AreEqual("15px", style.GetPaddingLeft());
73+
}
74+
75+
[Test]
76+
public void InheritOnNonInheritablePropertyShouldResolveFromParent()
77+
{
78+
var source = @"<!DOCTYPE html>
79+
<html>
80+
<head><style>
81+
div.parent { border: 1px solid; padding: 8px; }
82+
div.child { border: inherit; padding: inherit; }
83+
</style></head>
84+
<body>
85+
<div class='parent'><div class='child'>Content</div></div>
86+
</body>
87+
</html>";
88+
var document = ParseDocument(source);
89+
var child = document.QuerySelector("div.child");
90+
var style = child.ComputeCurrentStyle();
91+
Assert.AreEqual("solid", style.GetBorderTopStyle());
92+
Assert.AreEqual("1px", style.GetBorderTopWidth());
93+
Assert.AreEqual("8px", style.GetPaddingTop());
94+
}
95+
96+
[Test]
97+
public void BorderInheritShouldSerializeCorrectly()
98+
{
99+
var html = @"<style>div { border: inherit }</style>";
100+
var dom = ParseDocument(html);
101+
var styleSheet = dom.StyleSheets[0] as ICssStyleSheet;
102+
var rule = styleSheet.Rules[0] as ICssStyleRule;
103+
Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-style"));
104+
Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-width"));
105+
Assert.AreEqual("inherit", rule.Style.GetPropertyValue("border-top-color"));
106+
}
28107
}
29108
}

src/AngleSharp.Css/DeclarationInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class DeclarationInfo
2121
public DeclarationInfo(String name, IValueConverter converter, PropertyFlags flags = PropertyFlags.None, ICssValue initialValue = null, String[] shorthands = null, String[] longhands = null)
2222
{
2323
Name = name;
24-
Converter = initialValue != null ? Or(converter, AssignInitial(initialValue)) : converter;
24+
Converter = initialValue != null || longhands?.Length > 0 ? Or(AssignInitial(initialValue), converter) : converter;
2525
Aggregator = converter as IValueAggregator;
2626
Flags = flags;
2727
InitialValue = initialValue;

src/AngleSharp.Css/Dom/Internal/CssStyleDeclaration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ private void ChangeDeclarations(IEnumerable<ICssProperty> decls, Predicate<ICssP
397397
if (removeExisting.Invoke(olddecl, newdecl))
398398
{
399399
_declarations.RemoveAt(i);
400+
skip = false;
400401
}
401402
else
402403
{

src/AngleSharp.Css/Extensions/DeclarationInfoExtensions.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ public static ICssValue Collapse(this DeclarationInfo info, IDeclarationFactory
1616
var initial = true;
1717
var unset = true;
1818
var child = true;
19+
var inherit = true;
1920

2021
foreach (var longhand in longhands)
2122
{
2223
initial = initial && longhand is CssInitialValue;
2324
unset = unset && longhand is CssUnsetValue;
2425
child = child && longhand is CssChildValue;
26+
inherit = inherit && longhand is CssInheritValue;
2527
}
2628

2729
if (initial)
@@ -32,6 +34,10 @@ public static ICssValue Collapse(this DeclarationInfo info, IDeclarationFactory
3234
{
3335
return new CssUnsetValue(info.InitialValue);
3436
}
37+
else if (inherit)
38+
{
39+
return CssInheritValue.Instance;
40+
}
3541
else if (child)
3642
{
3743
return ((CssChildValue)longhands[0]).Parent;
@@ -58,6 +64,19 @@ public static ICssValue[] Expand(this DeclarationInfo info, IDeclarationFactory
5864
.OfType<ICssValue>()
5965
.ToArray();
6066
}
67+
else if (value is CssInheritValue)
68+
{
69+
return Enumerable
70+
.Repeat(value, longhands.Length)
71+
.ToArray();
72+
}
73+
else if (value is CssUnsetValue)
74+
{
75+
return longhands
76+
.Select(name => new CssUnsetValue(factory.Create(name)?.InitialValue))
77+
.OfType<ICssValue>()
78+
.ToArray();
79+
}
6180

6281
return info.Aggregator?.Split(value);
6382
}

0 commit comments

Comments
 (0)