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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions src/AngleSharp.Css/Extensions/ElementExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,17 @@ public static String GetInnerText(this IElement element)
hidden = true;
}

// Build the style collection once and cache computed styles for the
// entire traversal to avoid redundant stylesheet enumeration and
// repeated selector matching for the same elements.
var document = element.Owner;
var window = document?.DefaultView;
var styleCollection = window != null ? window.GetStyleCollection() : null;
var styleCache = new Dictionary<IElement, ICssStyleDeclaration>();

if (!hidden.HasValue)
{
var css = element.ComputeCurrentStyle();
var css = ComputeCachedStyle(element, styleCollection, styleCache);

if (!String.IsNullOrEmpty(css?.GetDisplay()))
{
Expand All @@ -67,7 +75,10 @@ public static String GetInnerText(this IElement element)
var offset = 0;
var sb = StringBuilderPool.Obtain();
var requiredLineBreakCounts = new Dictionary<Int32, Int32>();
InnerTextCollection(element, sb, requiredLineBreakCounts, element.ParentElement?.ComputeCurrentStyle());
var parentStyle = element.ParentElement != null
? ComputeCachedStyle(element.ParentElement, styleCollection, styleCache)
: null;
InnerTextCollection(element, sb, requiredLineBreakCounts, parentStyle, styleCollection, styleCache);

// Remove any runs of consecutive required line break count items at the start or end of results.
requiredLineBreakCounts.Remove(0);
Expand All @@ -87,6 +98,36 @@ public static String GetInnerText(this IElement element)
return element.TextContent;
}

private static ICssStyleDeclaration ComputeCachedStyle(IElement element, IStyleCollection styleCollection, Dictionary<IElement, ICssStyleDeclaration> cache)
{
if (styleCollection == null)
{
return null;
}

if (cache.TryGetValue(element, out var cached))
{
return cached;
}

ICssStyleDeclaration style;
var parent = element.ParentElement;

if (parent != null && cache.TryGetValue(parent, out var parentStyle))
{
// Parent already computed — use incremental computation that
// skips the O(depth) ancestor walk.
style = styleCollection.ComputeDeclarationsWithParent(element, parentStyle);
}
else
{
style = styleCollection.ComputeDeclarations(element);
}

cache[element] = style;
return style;
}

/// <summary>
/// Sets the innerText of an element.
/// </summary>
Expand Down Expand Up @@ -149,17 +190,17 @@ public static void SetInnerText(this IElement element, String value)
}
}

private static void InnerTextCollection(INode node, StringBuilder sb, Dictionary<Int32, Int32> requiredLineBreakCounts, ICssStyleDeclaration parentStyle)
private static void InnerTextCollection(INode node, StringBuilder sb, Dictionary<Int32, Int32> requiredLineBreakCounts, ICssStyleDeclaration parentStyle, IStyleCollection styleCollection, Dictionary<IElement, ICssStyleDeclaration> styleCache)
{
if (HasCssBox(node))
{
var element = node as IElement;
var elementCss = element?.ComputeCurrentStyle();
ItcInCssBox(elementCss, parentStyle, node, sb, requiredLineBreakCounts);
var elementCss = element != null ? ComputeCachedStyle(element, styleCollection, styleCache) : null;
ItcInCssBox(elementCss, parentStyle, node, sb, requiredLineBreakCounts, styleCollection, styleCache);
}
}

private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDeclaration parentStyle, INode node, StringBuilder sb, Dictionary<Int32, Int32> requiredLineBreakCounts)
private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDeclaration parentStyle, INode node, StringBuilder sb, Dictionary<Int32, Int32> requiredLineBreakCounts, IStyleCollection styleCollection, Dictionary<IElement, ICssStyleDeclaration> styleCache)
{
var elementHidden = new Nullable<Boolean>();

Expand Down Expand Up @@ -188,7 +229,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl

foreach (var child in node.ChildNodes)
{
InnerTextCollection(child, sb, requiredLineBreakCounts, elementStyle);
InnerTextCollection(child, sb, requiredLineBreakCounts, elementStyle, styleCollection, styleCache);
}

if (node is IText textElement)
Expand All @@ -206,7 +247,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl
{
if (node.NextSibling is IElement nextSibling)
{
var nextSiblingCss = nextSibling.ComputeCurrentStyle();
var nextSiblingCss = ComputeCachedStyle(nextSibling, styleCollection, styleCache);

if (nextSibling is IHtmlTableCellElement && String.IsNullOrEmpty(nextSiblingCss.GetDisplay()) || nextSiblingCss.GetDisplay() == CssKeywords.TableCell)
{
Expand All @@ -218,7 +259,7 @@ private static void ItcInCssBox(ICssStyleDeclaration elementStyle, ICssStyleDecl
{
if (node.NextSibling is IElement nextSibling)
{
var nextSiblingCss = nextSibling.ComputeCurrentStyle();
var nextSiblingCss = ComputeCachedStyle(nextSibling, styleCollection, styleCache);

if (nextSibling is IHtmlTableRowElement && String.IsNullOrEmpty(nextSiblingCss.GetDisplay()) || nextSiblingCss.GetDisplay() == CssKeywords.TableRow)
{
Expand Down
25 changes: 25 additions & 0 deletions src/AngleSharp.Css/Extensions/StyleCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,31 @@ public static ICssStyleDeclaration ComputeCascadedStyle(this IStyleCollection st
return computedStyle;
}

/// <summary>
/// Computes the declarations for the given element using a pre-computed
/// parent style for inheritance, avoiding the O(depth) ancestor walk.
/// </summary>
/// <param name="styles">The styles to use.</param>
/// <param name="element">The element that is questioned.</param>
/// <param name="parentComputedStyle">The parent's already-computed style.</param>
/// <returns>The style declaration containing all the declarations.</returns>
internal static ICssStyleDeclaration ComputeDeclarationsWithParent(this IStyleCollection styles, IElement element, ICssStyleDeclaration parentComputedStyle)
{
var ctx = element.Owner?.Context;
var computedStyle = new CssStyleDeclaration(ctx);

// Element's own cascaded style (CSS rule matching + inline style).
computedStyle.SetDeclarations(styles.ComputeCascadedStyle(element));

// Inherit from the parent's already-computed style instead of walking
// all ancestors individually. The parent style already includes the
// full ancestor inheritance chain.
computedStyle.UpdateDeclarations(parentComputedStyle);

var context = new CssComputeContext(styles.Device, ctx, computedStyle);
return computedStyle.Compute(context);
}

#endregion

#region Helpers
Expand Down
Loading