Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
66 changes: 49 additions & 17 deletions Source/HtmlRenderer/Core/CssData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ private static void CollectIndexKeys(ISelector selector, List<(SelectorBucketKin
// re-derived at match time (see DoesSelectorMatch), including for synthesized
// pseudo-element boxes whose HtmlTag is null - the tag/class/id index can't
// safely represent that, so fall back to a full scan for these.
if (compoundSelector.Any(s => s is PseudoElementSelector || s is FirstChildSelector))
if (compoundSelector.Any(s => s is PseudoElementSelector || IsChildIndexedPseudoClass(s)))
{
keys.Add((SelectorBucketKind.Universal, string.Empty));
return;
Expand Down Expand Up @@ -629,6 +629,14 @@ private static bool DoesSelectorMatch(AttrAvailableSelector attrAvailableSelecto
return false;
}

// Selectors 4 §6.3, the s flag compares the value case-sensitively
private static StringComparison ValueComparison(AttrSelectorBase selector)
{
return selector.CaseSensitivity == AttrCaseSensitivity.Sensitive
? StringComparison.Ordinal
: StringComparison.InvariantCultureIgnoreCase;
}

private static bool DoesSelectorMatch(AttrMatchSelector attrMatchSelector, CssBox box)
{
if (box == null || box.HtmlTag == null || box.HtmlTag.Attributes == null)
Expand All @@ -640,7 +648,7 @@ private static bool DoesSelectorMatch(AttrMatchSelector attrMatchSelector, CssBo
{
if (attribute.Key.Equals(attrMatchSelector.Attribute, StringComparison.InvariantCultureIgnoreCase))
{
return attribute.Value.Equals(attrMatchSelector.Value, StringComparison.InvariantCultureIgnoreCase);
return attribute.Value.Equals(attrMatchSelector.Value, ValueComparison(attrMatchSelector));
}
}

Expand All @@ -660,7 +668,7 @@ private static bool DoesSelectorMatch(AttrListSelector attrListSelector, CssBox
{
var attributeValues = attribute.Value.Split(' ').Where(x => x.Length > 0).ToArray();

return attributeValues.Any(value => value.Equals(attrListSelector.Value, StringComparison.InvariantCultureIgnoreCase));
return attributeValues.Any(value => value.Equals(attrListSelector.Value, ValueComparison(attrListSelector)));
}
}

Expand All @@ -678,7 +686,7 @@ private static bool DoesSelectorMatch(AttrContainsSelector attrContainsSelector,
{
if (attribute.Key.Equals(attrContainsSelector.Attribute, StringComparison.InvariantCultureIgnoreCase))
{
return attribute.Value.IndexOf(attrContainsSelector.Value, StringComparison.InvariantCultureIgnoreCase) >= 0;
return attribute.Value.IndexOf(attrContainsSelector.Value, ValueComparison(attrContainsSelector)) >= 0;
}
}

Expand All @@ -690,7 +698,7 @@ private static bool DoesSelectorMatch(AttrBeginsSelector s, CssBox box)
if (box == null || box.HtmlTag == null || box.HtmlTag.Attributes == null) return false;
foreach (var attr in box.HtmlTag.Attributes)
if (attr.Key.Equals(s.Attribute, StringComparison.InvariantCultureIgnoreCase))
return attr.Value.StartsWith(s.Value, StringComparison.InvariantCultureIgnoreCase);
return attr.Value.StartsWith(s.Value, ValueComparison(s));
return false;
}

Expand All @@ -699,7 +707,7 @@ private static bool DoesSelectorMatch(AttrEndsSelector s, CssBox box)
if (box == null || box.HtmlTag == null || box.HtmlTag.Attributes == null) return false;
foreach (var attr in box.HtmlTag.Attributes)
if (attr.Key.Equals(s.Attribute, StringComparison.InvariantCultureIgnoreCase))
return attr.Value.EndsWith(s.Value, StringComparison.InvariantCultureIgnoreCase);
return attr.Value.EndsWith(s.Value, ValueComparison(s));
return false;
}

Expand All @@ -708,8 +716,8 @@ private static bool DoesSelectorMatch(AttrHyphenSelector s, CssBox box)
if (box == null || box.HtmlTag == null || box.HtmlTag.Attributes == null) return false;
foreach (var attr in box.HtmlTag.Attributes)
if (attr.Key.Equals(s.Attribute, StringComparison.InvariantCultureIgnoreCase))
return attr.Value.Equals(s.Value, StringComparison.InvariantCultureIgnoreCase)
|| attr.Value.StartsWith(s.Value + "-", StringComparison.InvariantCultureIgnoreCase);
return attr.Value.Equals(s.Value, ValueComparison(s))
|| attr.Value.StartsWith(s.Value + "-", ValueComparison(s));
return false;
}

Expand All @@ -724,6 +732,15 @@ private static bool DoesSelectorMatch(PseudoClassSelector pseudoClassSelector, C
if (pseudoClassSelector.Class == "root")
return box.HtmlTag != null && box.HtmlTag.Name.Equals("html", StringComparison.InvariantCultureIgnoreCase);

// Selectors 4 §13.3 and §13.4, the bare forms are their nth-*() function with An+B = 1
switch (pseudoClassSelector.Class)
{
case "first-child": return MatchesChildPosition(box, 0, 1, AllSelector.Create(), false, false);
case "last-child": return MatchesChildPosition(box, 0, 1, AllSelector.Create(), false, true);
case "first-of-type": return MatchesChildPosition(box, 0, 1, AllSelector.Create(), true, false);
case "last-of-type": return MatchesChildPosition(box, 0, 1, AllSelector.Create(), true, true);
}

// CSS 2.1 §5.11.2. There's no history, every link is unvisited and :visited never matches.
return pseudoClassSelector.Class == "link" && box.IsClickable;
}
Expand All @@ -740,10 +757,15 @@ private static bool DoesSelectorMatch(PseudoElementSelector pseudoElementSelecto
return false;
}

/// <summary>
/// The child-indexed pseudo-classes, Selectors 4 §13.3 and §13.4, including the bare forms like
/// :first-child, which PseudoClassSelectorFactory creates with Step 0 and Offset 1.
/// </summary>
private static bool IsChildIndexedPseudoClass(ISelector selector)
{
if (selector is ChildSelector) return true;
if (!(selector is PseudoClassSelector pseudoClass)) return false;
return pseudoClass.Class == "first-child" || pseudoClass.Class == "last-child"
|| pseudoClass.Class == "first-of-type" || pseudoClass.Class == "last-of-type";
}

/// <summary>The child-indexed pseudo-classes, Selectors 4 §13.3 and §13.4.</summary>
private static bool DoesSelectorMatch(ChildSelector childSelector, CssBox box)
{
if (box == null || box.HtmlTag == null)
Expand All @@ -756,6 +778,18 @@ private static bool DoesSelectorMatch(ChildSelector childSelector, CssBox box)
return DoesColumnSelectorMatch(childSelector, box, childSelector is LastColumnSelector);
}

var sameTypeOnly = childSelector is FirstTypeSelector || childSelector is LastTypeSelector;
var fromEnd = childSelector is LastChildSelector || childSelector is LastTypeSelector;
return MatchesChildPosition(box, childSelector.Step, childSelector.Offset, childSelector.Kind, sameTypeOnly, fromEnd);
}

private static bool MatchesChildPosition(CssBox box, int step, int offset, ISelector of, bool sameTypeOnly, bool fromEnd)
{
if (box == null || box.HtmlTag == null)
{
return false;
}

var parentBox = DomUtils.GetNearestParentElementBox(box);
if (parentBox == null)
{
Expand All @@ -764,15 +798,14 @@ private static bool DoesSelectorMatch(ChildSelector childSelector, CssBox box)

IEnumerable<CssBox> siblingBoxes = parentBox.Boxes.Where(b => b.HtmlTag != null);

var sameTypeOnly = childSelector is FirstTypeSelector || childSelector is LastTypeSelector;
if (sameTypeOnly)
{
siblingBoxes = siblingBoxes.Where(b =>
b.HtmlTag.Name.Equals(box.HtmlTag.Name, StringComparison.InvariantCultureIgnoreCase));
}

// The "of S" filter of :nth-child()/:nth-last-child(). Without it Kind is AllSelector.
siblingBoxes = siblingBoxes.Where(b => DoesSelectorMatch(childSelector.Kind, b));
siblingBoxes = siblingBoxes.Where(b => DoesSelectorMatch(of, b));

var siblings = siblingBoxes.ToList();
var index = siblings.IndexOf(box);
Expand All @@ -781,10 +814,9 @@ private static bool DoesSelectorMatch(ChildSelector childSelector, CssBox box)
return false;
}

var fromEndPosition = childSelector is LastChildSelector || childSelector is LastTypeSelector;
var position = fromEndPosition ? siblings.Count - index : index + 1;
var position = fromEnd ? siblings.Count - index : index + 1;

return MatchesAnPlusB(position, childSelector.Step, childSelector.Offset);
return MatchesAnPlusB(position, step, offset);
}

/// <summary>
Expand Down
12 changes: 8 additions & 4 deletions Source/HtmlRenderer/Core/CssDefaults.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ @media print {
{ "font-size", "medium" },
{ "font-style", "normal" },
{ "font-variant", "normal" },
{ "font-variant-caps", "normal" },
{ "font-weight", "normal" },
{ "font-stretch", "normal" },
{ "float", "none" },
Expand Down Expand Up @@ -196,6 +197,7 @@ @media print {
{ "orphans", "2" },
{ "page", "auto" },
{ "text-align", "" },
{ "text-align-all", "" },
{ "text-decoration-line", "" },
{ "text-indent", "0" },
{ "top", "auto" },
Expand All @@ -207,6 +209,8 @@ @media print {
{ "word-break", "normal" },
{ "visibility", "visible" },
{ "white-space", "normal" },
{ "white-space-collapse", "collapse" },
{ "text-wrap-mode", "wrap" },
};

/// <summary>
Expand All @@ -219,10 +223,10 @@ @media print {
"border-spacing", "border-collapse",
"color",
"empty-cells",
"white-space",
"white-space", "white-space-collapse", "text-wrap-mode",
"visibility",
"text-indent", "text-align",
"font-family", "font-size", "font-style", "font-variant", "font-weight", "font-stretch",
"text-indent", "text-align", "text-align-all",
"font-family", "font-size", "font-style", "font-variant", "font-variant-caps", "font-weight", "font-stretch",
"list-style-image", "list-style-position", "list-style-type", "list-style",
"line-height",
"word-break",
Expand All @@ -237,7 +241,7 @@ @media print {
internal static string GetInitialValue(string propertyName)
{
string value;
return InitialValues.TryGetValue(propertyName, out value) ? value : null;
return InitialValues.TryGetValue(Utils.CssUtils.ToPhysicalPropertyName(propertyName), out value) ? value : null;
}
}
}
97 changes: 0 additions & 97 deletions Source/HtmlRenderer/Core/CssEngine/AspectRatioGrammar.cs

This file was deleted.

Loading
Loading