| /* |
| * Copyright (C) 2025-2026 Samuel Weinig <[email protected]> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "StyleDifference.h" |
| |
| #include "InlineTextBoxStyle.h" |
| #include "RenderStyleConstants.h" |
| #include "RenderStyle+GettersInlines.h" |
| #include "StyleExtractor.h" |
| |
| namespace WebCore { |
| namespace Style { |
| |
| class DifferenceFunctions final { |
| public: |
| // MARK: DifferenceResult::Layout |
| |
| static bool positionChangeIsMovementOnly(const InsetBox& a, const InsetBox& b, const PreferredSize& width) |
| { |
| // If any unit types are different, then we can't guarantee |
| // that this was just a movement. |
| if (!a.left().hasSameType(b.left()) |
| || !a.right().hasSameType(b.right()) |
| || !a.top().hasSameType(b.top()) |
| || !a.bottom().hasSameType(b.bottom())) |
| return false; |
| |
| // Only one unit can be non-auto in the horizontal direction and |
| // in the vertical direction. Otherwise the adjustment of values |
| // is changing the size of the box. |
| if (!a.left().isAuto() && !a.right().isAuto()) |
| return false; |
| if (!a.top().isAuto() && !a.bottom().isAuto()) |
| return false; |
| // If our width is auto and left or right is specified then this |
| // is not just a movement - we need to resize to our container. |
| if ((!a.left().isAuto() || !a.right().isAuto()) && width.isIntrinsicOrLegacyIntrinsicOrAuto()) |
| return false; |
| |
| // One of the units is fixed or percent in both directions and stayed |
| // that way in the new style. Therefore all we are doing is moving. |
| return true; |
| } |
| |
| static bool changeAffectsVisualOverflow(const RenderStyle& a, const RenderStyle& b) |
| { |
| auto nonInheritedDataChangeAffectsVisualOverflow = [&] { |
| if (&a.nonInheritedData() == &b.nonInheritedData()) |
| return false; |
| |
| if (a.nonInheritedData().miscData.ptr() != b.nonInheritedData().miscData.ptr() |
| && a.nonInheritedData().miscData->boxShadow != b.nonInheritedData().miscData->boxShadow) |
| return true; |
| |
| if (a.nonInheritedData().backgroundData.ptr() != b.nonInheritedData().backgroundData.ptr()) { |
| auto aHasOutlineInVisualOverflow = a.hasOutlineInVisualOverflow(); |
| auto bHasOutlineInVisualOverflow = b.hasOutlineInVisualOverflow(); |
| if (aHasOutlineInVisualOverflow != bHasOutlineInVisualOverflow |
| || (aHasOutlineInVisualOverflow && bHasOutlineInVisualOverflow && a.usedOutlineSize() != b.usedOutlineSize())) |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| auto textDecorationsDiffer = [&] { |
| if (a.inheritedFlags().textDecorationLineInEffect != b.inheritedFlags().textDecorationLineInEffect) |
| return true; |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData() && a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr()) { |
| if (a.nonInheritedData().rareData->textDecorationStyle != b.nonInheritedData().rareData->textDecorationStyle |
| || a.nonInheritedData().rareData->textDecorationThickness != b.nonInheritedData().rareData->textDecorationThickness) |
| return true; |
| } |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData()) { |
| if (a.inheritedRareData().textUnderlineOffset != b.inheritedRareData().textUnderlineOffset |
| || a.inheritedRareData().textUnderlinePosition != b.inheritedRareData().textUnderlinePosition) |
| return true; |
| } |
| |
| return false; |
| }; |
| |
| if (nonInheritedDataChangeAffectsVisualOverflow()) |
| return true; |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData() |
| && a.inheritedRareData().textShadow != b.inheritedRareData().textShadow) |
| return true; |
| |
| if (textDecorationsDiffer()) { |
| // Underlines are always drawn outside of their textbox bounds when text-underline-position: under; |
| // is specified. We can take an early out here. |
| if (isAlignedForUnder(a) || isAlignedForUnder(b)) |
| return true; |
| |
| if (inkOverflowForDecorations(a) != inkOverflowForDecorations(b)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static bool svgDataChangeRequiresLayout(const SVGData& a, const SVGData& b) |
| { |
| // Markers influence layout, as marker boundaries are cached in RenderSVGPath. |
| if (a.markerResourceData != b.markerResourceData) |
| return true; |
| |
| // All text related properties influence layout. |
| if (a.inheritedFlags.textAnchor != b.inheritedFlags.textAnchor |
| || a.inheritedFlags.glyphOrientationHorizontal != b.inheritedFlags.glyphOrientationHorizontal |
| || a.inheritedFlags.glyphOrientationVertical != b.inheritedFlags.glyphOrientationVertical |
| || a.nonInheritedFlags.alignmentBaseline != b.nonInheritedFlags.alignmentBaseline |
| || a.nonInheritedFlags.dominantBaseline != b.nonInheritedFlags.dominantBaseline) |
| return true; |
| |
| // Text related properties influence layout. |
| if (a.miscData->baselineShift != b.miscData->baselineShift) |
| return true; |
| |
| // The x and y properties influence layout. |
| if (a.layoutData != b.layoutData) |
| return true; |
| |
| // Some stroke properties influence layout, as the cached stroke boundaries need to be recalculated. |
| if (!a.strokeData->stroke.hasSameType(b.strokeData->stroke) |
| || a.strokeData->stroke.urlDisregardingType() != b.strokeData->stroke.urlDisregardingType() |
| || a.strokeData->strokeDashArray != b.strokeData->strokeDashArray |
| || a.strokeData->strokeDashOffset != b.strokeData->strokeDashOffset |
| || !a.strokeData->visitedLinkStroke.hasSameType(b.strokeData->visitedLinkStroke) |
| || a.strokeData->visitedLinkStroke.urlDisregardingType() != b.strokeData->visitedLinkStroke.urlDisregardingType()) |
| return true; |
| |
| // vector-effect influences layout. |
| if (a.nonInheritedFlags.vectorEffect != b.nonInheritedFlags.vectorEffect) |
| return true; |
| |
| return false; |
| } |
| |
| static bool miscDataChangeRequiresLayout(const NonInheritedMiscData& a, const NonInheritedMiscData& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| ASSERT(&a != &b); |
| |
| if (a.usedAppearance != b.usedAppearance |
| || a.textOverflow != b.textOverflow) |
| return true; |
| |
| if (a.deprecatedFlexibleBox != b.deprecatedFlexibleBox) |
| return true; |
| |
| if (a.flexibleBox != b.flexibleBox) |
| return true; |
| |
| if (a.order != b.order |
| || a.alignContent != b.alignContent |
| || a.alignItems != b.alignItems |
| || a.alignSelf != b.alignSelf |
| || a.justifyContent != b.justifyContent |
| || a.justifyItems != b.justifyItems |
| || a.justifySelf != b.justifySelf) |
| return true; |
| |
| if (a.multiCol != b.multiCol) |
| return true; |
| |
| if (a.transform.ptr() != b.transform.ptr()) { |
| if (a.transform->hasTransform() != b.transform->hasTransform()) |
| return true; |
| if (*a.transform != *b.transform) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Transform); |
| // Don't return; keep looking for another change |
| } |
| } |
| |
| if (a.opacity.isOpaque() != b.opacity.isOpaque()) { |
| // FIXME: We would like to use SimplifiedLayout here, but we can't quite do that yet. |
| // We need to make sure SimplifiedLayout can operate correctly on RenderInlines (we will need |
| // to add a selfNeedsSimplifiedLayout bit in order to not get confused and taint every line). |
| // In addition we need to solve the floating object issue when layers come and go. Right now |
| // a full layout is necessary to keep floating object lists sane. |
| return true; |
| } |
| |
| if (a.hasFilters() != b.hasFilters()) |
| return true; |
| |
| if (a.aspectRatio != b.aspectRatio) |
| return true; |
| |
| return false; |
| } |
| |
| static bool rareDataChangeRequiresLayout(const NonInheritedRareData& a, const NonInheritedRareData& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| ASSERT(&a != &b); |
| |
| if (a.lineClamp != b.lineClamp || a.initialLetter != b.initialLetter) |
| return true; |
| |
| if (a.shapeMargin != b.shapeMargin) |
| return true; |
| |
| if (a.columnGap != b.columnGap || a.rowGap != b.rowGap) |
| return true; |
| |
| if (a.boxReflect != b.boxReflect) |
| return true; |
| |
| // If the counter directives change, trigger a relayout to re-calculate counter values and rebuild the counter node tree. |
| if (a.usedCounterDirectives != b.usedCounterDirectives) |
| return true; |
| |
| if (a.scale != b.scale || a.rotate != b.rotate || a.translate != b.translate) |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Transform); |
| |
| if (a.offsetPath != b.offsetPath |
| || a.offsetPosition != b.offsetPosition |
| || a.offsetDistance != b.offsetDistance |
| || a.offsetAnchor != b.offsetAnchor |
| || a.offsetRotate != b.offsetRotate) |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Transform); |
| |
| if (a.grid != b.grid |
| || a.gridItem != b.gridItem) |
| return true; |
| |
| if (a.willChange != b.willChange) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::WillChange); |
| // Don't return; keep looking for another change |
| } |
| |
| if (a.breakBefore != b.breakBefore || a.breakAfter != b.breakAfter || a.breakInside != b.breakInside) |
| return true; |
| |
| if (a.isolation != b.isolation) { |
| // Ideally this would trigger a cheaper layout that just updates layer z-order trees (webkit.org/b/190088). |
| return true; |
| } |
| |
| if (a.backdropFilter->backdropFilter.isNone() != b.backdropFilter->backdropFilter.isNone()) |
| return true; |
| |
| #if HAVE(CORE_MATERIAL) |
| if (a.appleVisualEffect != b.appleVisualEffect) |
| return true; |
| #endif |
| |
| if (a.inputSecurity != b.inputSecurity) |
| return true; |
| |
| if (a.usedContain().contains(ContainValue::Size) != b.usedContain().contains(ContainValue::Size) |
| || a.usedContain().contains(ContainValue::InlineSize) != b.usedContain().contains(ContainValue::InlineSize) |
| || a.usedContain().contains(ContainValue::Layout) != b.usedContain().contains(ContainValue::Layout)) |
| return true; |
| |
| // content-visibility:hidden turns on contain:size which requires relayout. |
| if ((static_cast<ContentVisibility>(a.contentVisibility) == ContentVisibility::Hidden) != (static_cast<ContentVisibility>(b.contentVisibility) == ContentVisibility::Hidden)) |
| return true; |
| |
| if (a.scrollPadding != b.scrollPadding) |
| return true; |
| |
| if (a.scrollSnapType != b.scrollSnapType) |
| return true; |
| |
| if (a.containIntrinsicWidth != b.containIntrinsicWidth || a.containIntrinsicHeight != b.containIntrinsicHeight) |
| return true; |
| |
| if (a.marginTrim != b.marginTrim) |
| return true; |
| |
| if (a.scrollbarGutter != b.scrollbarGutter) |
| return true; |
| |
| if (a.scrollbarWidth != b.scrollbarWidth) |
| return true; |
| |
| if (a.textBoxTrim != b.textBoxTrim) |
| return true; |
| |
| if (a.maxLines != b.maxLines) |
| return true; |
| |
| if (a.overflowContinue != b.overflowContinue) |
| return true; |
| |
| // CSS Anchor Positioning. |
| if (a.anchorScope != b.anchorScope || a.positionArea != b.positionArea) |
| return true; |
| |
| if (a.fieldSizing != b.fieldSizing) |
| return true; |
| |
| return false; |
| } |
| |
| static bool rareInheritedDataChangeRequiresLayout(const InheritedRareData& a, const InheritedRareData& b) |
| { |
| ASSERT(&a != &b); |
| |
| if (a.textIndent != b.textIndent |
| || a.textAlignLast != b.textAlignLast |
| || a.textJustify != b.textJustify |
| || a.textBoxEdge != b.textBoxEdge |
| || a.lineFitEdge != b.lineFitEdge |
| || a.usedZoom != b.usedZoom |
| || a.textZoom != b.textZoom |
| #if ENABLE(TEXT_AUTOSIZING) |
| || a.textSizeAdjust != b.textSizeAdjust |
| #endif |
| || a.wordBreak != b.wordBreak |
| || a.overflowWrap != b.overflowWrap |
| || a.nbspMode != b.nbspMode |
| || a.lineBreak != b.lineBreak |
| || a.textSecurity != b.textSecurity |
| || a.hyphens != b.hyphens |
| || a.hyphenateLimitBefore != b.hyphenateLimitBefore |
| || a.hyphenateLimitAfter != b.hyphenateLimitAfter |
| || a.hyphenateCharacter != b.hyphenateCharacter |
| || a.rubyPosition != b.rubyPosition |
| || a.rubyAlign != b.rubyAlign |
| || a.rubyOverhang != b.rubyOverhang |
| || a.textCombine != b.textCombine |
| || a.textEmphasisStyle != b.textEmphasisStyle |
| || a.textEmphasisPosition != b.textEmphasisPosition |
| || a.tabSize != b.tabSize |
| || a.lineBoxContain != b.lineBoxContain |
| || a.lineGrid != b.lineGrid |
| || a.imageOrientation != b.imageOrientation |
| || a.lineSnap != b.lineSnap |
| || a.lineAlign != b.lineAlign |
| || a.hangingPunctuation != b.hangingPunctuation |
| || a.usedContentVisibility != b.usedContentVisibility |
| #if ENABLE(WEBKIT_OVERFLOW_SCROLLING_CSS_PROPERTY) |
| || a.overflowScrolling != b.overflowScrolling |
| #endif |
| || a.listStyleType != b.listStyleType |
| || a.listStyleImage != b.listStyleImage |
| || a.blockEllipsis != b.blockEllipsis) |
| return true; |
| |
| if (a.textStrokeWidth != b.textStrokeWidth) |
| return true; |
| |
| // These properties affect the cached stroke bounding box rects. |
| if (a.capStyle != b.capStyle |
| || a.joinStyle != b.joinStyle |
| || a.strokeWidth != b.strokeWidth |
| || a.strokeMiterLimit != b.strokeMiterLimit) |
| return true; |
| |
| if (a.quotes != b.quotes) |
| return true; |
| |
| return false; |
| } |
| |
| static bool changeRequiresLayout(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| if (&a.svgData() != &b.svgData() && svgDataChangeRequiresLayout(a.svgData(), b.svgData())) |
| return true; |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData()) { |
| if (a.nonInheritedData().boxData.ptr() != b.nonInheritedData().boxData.ptr()) { |
| if (a.nonInheritedData().boxData->width != b.nonInheritedData().boxData->width |
| || a.nonInheritedData().boxData->minWidth != b.nonInheritedData().boxData->minWidth |
| || a.nonInheritedData().boxData->maxWidth != b.nonInheritedData().boxData->maxWidth |
| || a.nonInheritedData().boxData->height != b.nonInheritedData().boxData->height |
| || a.nonInheritedData().boxData->minHeight != b.nonInheritedData().boxData->minHeight |
| || a.nonInheritedData().boxData->maxHeight != b.nonInheritedData().boxData->maxHeight) |
| return true; |
| |
| if (a.nonInheritedData().boxData->verticalAlign != b.nonInheritedData().boxData->verticalAlign) |
| return true; |
| |
| if (a.nonInheritedData().boxData->boxSizing != b.nonInheritedData().boxData->boxSizing) |
| return true; |
| |
| if (a.nonInheritedData().boxData->hasAutoUsedZIndex != b.nonInheritedData().boxData->hasAutoUsedZIndex) |
| return true; |
| } |
| |
| if (a.nonInheritedData().surroundData.ptr() != b.nonInheritedData().surroundData.ptr()) { |
| if (a.nonInheritedData().surroundData->margin != b.nonInheritedData().surroundData->margin) |
| return true; |
| |
| if (a.nonInheritedData().surroundData->padding != b.nonInheritedData().surroundData->padding) |
| return true; |
| |
| // If our border widths change, then we need to layout. Other changes to borders only necessitate a repaint. |
| if (a.usedBorderLeftWidth() != b.usedBorderLeftWidth() |
| || a.usedBorderTopWidth() != b.usedBorderTopWidth() |
| || a.usedBorderBottomWidth() != b.usedBorderBottomWidth() |
| || a.usedBorderRightWidth() != b.usedBorderRightWidth()) |
| return true; |
| |
| if (a.position() != PositionType::Static) { |
| if (a.nonInheritedData().surroundData->inset != b.nonInheritedData().surroundData->inset) { |
| // FIXME: We would like to use SimplifiedLayout for relative positioning, but we can't quite do that yet. |
| // We need to make sure SimplifiedLayout can operate correctly on RenderInlines (we will need |
| // to add a selfNeedsSimplifiedLayout bit in order to not get confused and taint every line). |
| if (a.position() != PositionType::Absolute) |
| return true; |
| |
| // Optimize for the case where a positioned layer is moving but not changing size. |
| if (!positionChangeIsMovementOnly(a.nonInheritedData().surroundData->inset, b.nonInheritedData().surroundData->inset, a.nonInheritedData().boxData->width)) |
| return true; |
| } |
| } |
| } |
| } |
| |
| // FIXME: We should add an optimized form of layout that just recomputes visual overflow. |
| if (changeAffectsVisualOverflow(a, b)) |
| return true; |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData()) { |
| if (a.nonInheritedData().miscData.ptr() != b.nonInheritedData().miscData.ptr() |
| && miscDataChangeRequiresLayout(*a.nonInheritedData().miscData, *b.nonInheritedData().miscData, changedContextSensitiveProperties)) |
| return true; |
| |
| if (a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr() |
| && rareDataChangeRequiresLayout(*a.nonInheritedData().rareData, *b.nonInheritedData().rareData, changedContextSensitiveProperties)) |
| return true; |
| } |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData() |
| && rareInheritedDataChangeRequiresLayout(a.inheritedRareData(), b.inheritedRareData())) |
| return true; |
| |
| if (&a.inheritedData() != &b.inheritedData()) { |
| if (a.inheritedData().lineHeight != b.inheritedData().lineHeight |
| #if ENABLE(TEXT_AUTOSIZING) |
| || a.inheritedData().specifiedLineHeight != b.inheritedData().specifiedLineHeight |
| #endif |
| || a.inheritedData().borderHorizontalSpacing != b.inheritedData().borderHorizontalSpacing |
| || a.inheritedData().borderVerticalSpacing != b.inheritedData().borderVerticalSpacing) |
| return true; |
| |
| if (a.inheritedData().fontData != b.inheritedData().fontData) |
| return true; |
| } |
| |
| if (a.inheritedFlags().boxDirection != b.inheritedFlags().boxDirection |
| || a.inheritedFlags().rtlOrdering != b.inheritedFlags().rtlOrdering |
| || a.nonInheritedFlags().position != b.nonInheritedFlags().position |
| || a.nonInheritedFlags().floating != b.nonInheritedFlags().floating |
| || a.nonInheritedFlags().originalDisplay != b.nonInheritedFlags().originalDisplay) |
| return true; |
| |
| if (static_cast<DisplayType>(a.nonInheritedFlags().effectiveDisplay) >= DisplayType::Table) { |
| if (a.inheritedFlags().borderCollapse != b.inheritedFlags().borderCollapse |
| || a.inheritedFlags().emptyCells != b.inheritedFlags().emptyCells |
| || a.inheritedFlags().captionSide != b.inheritedFlags().captionSide |
| || a.tableLayout() != b.tableLayout()) |
| return true; |
| |
| // In the collapsing border model, 'hidden' suppresses other borders, while 'none' |
| // does not, so these style differences can be width differences. |
| if (a.inheritedFlags().borderCollapse |
| && ((a.borderTopStyle() == BorderStyle::Hidden && b.borderTopStyle() == BorderStyle::None) |
| || (a.borderTopStyle() == BorderStyle::None && b.borderTopStyle() == BorderStyle::Hidden) |
| || (a.borderBottomStyle() == BorderStyle::Hidden && b.borderBottomStyle() == BorderStyle::None) |
| || (a.borderBottomStyle() == BorderStyle::None && b.borderBottomStyle() == BorderStyle::Hidden) |
| || (a.borderLeftStyle() == BorderStyle::Hidden && b.borderLeftStyle() == BorderStyle::None) |
| || (a.borderLeftStyle() == BorderStyle::None && b.borderLeftStyle() == BorderStyle::Hidden) |
| || (a.borderRightStyle() == BorderStyle::Hidden && b.borderRightStyle() == BorderStyle::None) |
| || (a.borderRightStyle() == BorderStyle::None && b.borderRightStyle() == BorderStyle::Hidden))) |
| return true; |
| } |
| |
| if (static_cast<DisplayType>(a.nonInheritedFlags().effectiveDisplay) == DisplayType::ListItem) { |
| if (a.inheritedFlags().listStylePosition != b.inheritedFlags().listStylePosition || a.inheritedRareData().listStyleType != b.inheritedRareData().listStyleType) |
| return true; |
| } |
| |
| if (a.inheritedFlags().textAlign != b.inheritedFlags().textAlign |
| || a.inheritedFlags().textTransform != b.inheritedFlags().textTransform |
| || a.inheritedFlags().whiteSpaceCollapse != b.inheritedFlags().whiteSpaceCollapse |
| || a.inheritedFlags().textWrapMode != b.inheritedFlags().textWrapMode |
| || a.inheritedFlags().textWrapStyle != b.inheritedFlags().textWrapStyle |
| || a.nonInheritedFlags().clear != b.nonInheritedFlags().clear |
| || a.nonInheritedFlags().unicodeBidi != b.nonInheritedFlags().unicodeBidi) |
| return true; |
| |
| if (a.writingMode() != b.writingMode()) |
| return true; |
| |
| // Overflow returns a layout hint. |
| if (a.nonInheritedFlags().overflowX != b.nonInheritedFlags().overflowX |
| || a.nonInheritedFlags().overflowY != b.nonInheritedFlags().overflowY) |
| return true; |
| |
| if ((a.usedVisibility() == Visibility::Collapse) != (b.usedVisibility() == Visibility::Collapse)) |
| return true; |
| |
| bool aHasFirstLineStyle = a.hasPseudoStyle(PseudoElementType::FirstLine); |
| if (aHasFirstLineStyle != b.hasPseudoStyle(PseudoElementType::FirstLine)) |
| return true; |
| |
| if (aHasFirstLineStyle) { |
| auto* aFirstLineStyle = a.getCachedPseudoStyle({ PseudoElementType::FirstLine }); |
| if (!aFirstLineStyle) |
| return true; |
| auto* bFirstLineStyle = b.getCachedPseudoStyle({ PseudoElementType::FirstLine }); |
| if (!bFirstLineStyle) |
| return true; |
| // FIXME: Not all first line style changes actually need layout. |
| if (*aFirstLineStyle != *bFirstLineStyle) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // MARK: DifferenceResult::LayoutOutOfFlowMovementOnly |
| |
| static bool changeRequiresOutOfFlowMovementLayoutOnly(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>&) |
| { |
| if (a.position() != PositionType::Absolute) |
| return false; |
| |
| // Optimize for the case where a out-of-flow box is moving but not changing size. |
| return a.nonInheritedData().surroundData->inset != b.nonInheritedData().surroundData->inset |
| && positionChangeIsMovementOnly(a.nonInheritedData().surroundData->inset, b.nonInheritedData().surroundData->inset, a.nonInheritedData().boxData->width); |
| } |
| |
| // MARK: DifferenceResult::RepaintLayer |
| |
| static bool miscDataChangeRequiresLayerRepaint(const NonInheritedMiscData& a, const NonInheritedMiscData& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| if (a.opacity != b.opacity) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Opacity); |
| // Don't return true; keep looking for another change. |
| } |
| |
| if (a.filter != b.filter) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Filter); |
| // Don't return true; keep looking for another change. |
| } |
| |
| // FIXME: In SVG this needs to trigger a layout. |
| if (a.mask != b.mask) |
| return true; |
| |
| return false; |
| } |
| |
| static bool rareDataChangeRequiresLayerRepaint(const NonInheritedRareData& a, const NonInheritedRareData& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| if (a.effectiveBlendMode != b.effectiveBlendMode) |
| return true; |
| |
| if (a.backdropFilter != b.backdropFilter) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Filter); |
| // Don't return true; keep looking for another change. |
| } |
| |
| // FIXME: In SVG this needs to trigger a layout. |
| if (a.maskBorder != b.maskBorder) |
| return true; |
| |
| return false; |
| } |
| |
| static bool changeRequiresLayerRepaint(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| // Resolver has ensured that zIndex is non-auto only if it's applicable. |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData()) { |
| if (a.nonInheritedData().boxData.ptr() != b.nonInheritedData().boxData.ptr()) { |
| if (a.nonInheritedData().boxData->usedZIndex() != b.nonInheritedData().boxData->usedZIndex()) |
| return true; |
| } |
| |
| if (a.position() != PositionType::Static) { |
| if (a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr()) { |
| if (a.nonInheritedData().rareData->clip != b.nonInheritedData().rareData->clip) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::ClipRect); |
| return true; |
| } |
| } |
| } |
| |
| if (a.nonInheritedData().miscData.ptr() != b.nonInheritedData().miscData.ptr() |
| && miscDataChangeRequiresLayerRepaint(*a.nonInheritedData().miscData, *b.nonInheritedData().miscData, changedContextSensitiveProperties)) |
| return true; |
| |
| if (a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr() |
| && rareDataChangeRequiresLayerRepaint(*a.nonInheritedData().rareData, *b.nonInheritedData().rareData, changedContextSensitiveProperties)) |
| return true; |
| } |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData() |
| && a.inheritedRareData().dynamicRangeLimit != b.inheritedRareData().dynamicRangeLimit) { |
| return true; |
| } |
| |
| #if HAVE(CORE_MATERIAL) |
| if (&a.inheritedRareData() != &b.inheritedRareData() |
| && a.inheritedRareData().usedAppleVisualEffectForSubtree != b.inheritedRareData().usedAppleVisualEffectForSubtree) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::Filter); |
| // Don't return true; keep looking for another change. |
| } |
| #endif |
| |
| bool currentColorDiffers = a.inheritedData().color != b.inheritedData().color; |
| if (currentColorDiffers) { |
| if (a.filter().hasFilterThatRequiresRepaintForCurrentColorChange() || a.backdropFilter().hasFilterThatRequiresRepaintForCurrentColorChange()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // MARK: DifferenceResult::Repaint |
| |
| static bool requiresPainting(const RenderStyle& style) |
| { |
| if (style.usedVisibility() == Visibility::Hidden) |
| return false; |
| if (style.opacity().isTransparent()) |
| return false; |
| return true; |
| } |
| |
| static bool isEquivalentForPainting(const BackgroundData& a, const BackgroundData& b, bool currentColorDiffers) |
| { |
| if (&a == &b) { |
| ASSERT(currentColorDiffers); |
| return !a.containsCurrentColor(); |
| } |
| |
| if (a.background != b.background || a.backgroundColor != b.backgroundColor) |
| return false; |
| if (currentColorDiffers && a.backgroundColor.containsCurrentColor()) |
| return false; |
| if (!a.outline.isVisible() && !b.outline.isVisible()) |
| return true; |
| if (currentColorDiffers && a.outline.outlineColor.containsCurrentColor()) |
| return false; |
| return a.outline == b.outline; |
| } |
| |
| static bool isEquivalentForPainting(const BorderData& a, const BorderData& b, bool currentColorDiffers) |
| { |
| if (&a == &b) { |
| ASSERT(currentColorDiffers); |
| return !a.containsCurrentColor(); |
| } |
| |
| if (a != b) |
| return false; |
| |
| if (!currentColorDiffers) |
| return true; |
| |
| return !a.containsCurrentColor(); |
| } |
| |
| static bool colorChangeRequiresRepaint(const Color& a, const Color& b, bool currentColorDiffers) |
| { |
| if (a != b) |
| return true; |
| |
| if (a.containsCurrentColor()) { |
| ASSERT(b.containsCurrentColor()); |
| return currentColorDiffers; |
| } |
| |
| return false; |
| } |
| |
| static bool svgDataChangeRequiresRepaint(const SVGData& a, const SVGData& b, bool currentColorDiffers) |
| { |
| if (&a == &b) { |
| ASSERT(currentColorDiffers); |
| return containsCurrentColor(a.strokeData->stroke) |
| || containsCurrentColor(a.strokeData->visitedLinkStroke) |
| || containsCurrentColor(a.miscData->floodColor) |
| || containsCurrentColor(a.miscData->lightingColor) |
| || containsCurrentColor(a.fillData->fill); // FIXME: Should this be checking fillData->visitedLinkFill as well? |
| } |
| |
| if (a.strokeData->strokeOpacity != b.strokeData->strokeOpacity |
| || colorChangeRequiresRepaint(a.strokeData->stroke.colorDisregardingType(), b.strokeData->stroke.colorDisregardingType(), currentColorDiffers) |
| || colorChangeRequiresRepaint(a.strokeData->visitedLinkStroke.colorDisregardingType(), b.strokeData->visitedLinkStroke.colorDisregardingType(), currentColorDiffers)) |
| return true; |
| |
| // Painting related properties only need repaints. |
| if (colorChangeRequiresRepaint(a.miscData->floodColor, b.miscData->floodColor, currentColorDiffers) |
| || a.miscData->floodOpacity != b.miscData->floodOpacity |
| || colorChangeRequiresRepaint(a.miscData->lightingColor, b.miscData->lightingColor, currentColorDiffers)) |
| return true; |
| |
| // If fill data changes, we just need to repaint. Fill boundaries are not influenced by this, only by the Path, that RenderSVGPath contains. |
| if (!a.fillData->fill.hasSameType(b.fillData->fill) |
| || colorChangeRequiresRepaint(a.fillData->fill.colorDisregardingType(), b.fillData->fill.colorDisregardingType(), currentColorDiffers) |
| || a.fillData->fill.urlDisregardingType() != b.fillData->fill.urlDisregardingType() |
| || a.fillData->fillOpacity != b.fillData->fillOpacity) |
| return true; |
| |
| // If gradient stops change, we just need to repaint. Style updates are already handled through RenderSVGGradientStop. |
| if (a.stopData != b.stopData) |
| return true; |
| |
| // Changes of these flags only cause repaints. |
| if (a.inheritedFlags.shapeRendering != b.inheritedFlags.shapeRendering |
| || a.inheritedFlags.clipRule != b.inheritedFlags.clipRule |
| || a.inheritedFlags.fillRule != b.inheritedFlags.fillRule |
| || a.inheritedFlags.colorInterpolation != b.inheritedFlags.colorInterpolation |
| || a.inheritedFlags.colorInterpolationFilters != b.inheritedFlags.colorInterpolationFilters) |
| return true; |
| |
| if (a.nonInheritedFlags.bufferedRendering != b.nonInheritedFlags.bufferedRendering) |
| return true; |
| |
| if (a.nonInheritedFlags.maskType != b.nonInheritedFlags.maskType) |
| return true; |
| |
| return false; |
| } |
| |
| static bool miscDataChangeRequiresRepaint(const NonInheritedMiscData& a, const NonInheritedMiscData& b, OptionSet<DifferenceContextSensitiveProperty>&) |
| { |
| if (a.userDrag != b.userDrag |
| || a.objectFit != b.objectFit |
| || a.objectPosition != b.objectPosition) |
| return true; |
| |
| return false; |
| } |
| |
| static bool rareDataChangeRequiresRepaint(const NonInheritedRareData& a, const NonInheritedRareData& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| if (a.shapeOutside != b.shapeOutside) |
| return true; |
| |
| // FIXME: this should probably be moved to changeRequiresLayerRepaint(). |
| if (a.clipPath != b.clipPath) { |
| changedContextSensitiveProperties.add(DifferenceContextSensitiveProperty::ClipPath); |
| // Don't return true; keep looking for another change. |
| } |
| |
| if (a.textDecorationStyle != b.textDecorationStyle || a.textDecorationColor != b.textDecorationColor || a.textDecorationThickness != b.textDecorationThickness) |
| return true; |
| |
| return false; |
| } |
| |
| static bool rareInheritedDataChangeRequiresRepaint(const InheritedRareData& a, const InheritedRareData& b) |
| { |
| return a.effectiveInert != b.effectiveInert |
| || a.userModify != b.userModify |
| || a.userSelect != b.userSelect |
| || a.appleColorFilter != b.appleColorFilter |
| || a.imageRendering != b.imageRendering |
| || a.accentColor != b.accentColor |
| || a.insideDefaultButton != b.insideDefaultButton |
| || a.insideSubmitButton != b.insideSubmitButton |
| #if ENABLE(DARK_MODE_CSS) |
| || a.colorScheme != b.colorScheme |
| #endif |
| ; |
| } |
| |
| inline static bool changedCustomPaintWatchedProperty(const RenderStyle& a, const NonInheritedRareData& aData, const RenderStyle& b, const NonInheritedRareData& bData) |
| { |
| auto& propertiesA = aData.customPaintWatchedProperties; |
| auto& propertiesB = bData.customPaintWatchedProperties; |
| |
| if (!propertiesA.isEmpty() || !propertiesB.isEmpty()) [[unlikely]] { |
| // FIXME: We should not need to use Extractor here. |
| Extractor extractor((Element*)nullptr); |
| auto& pool = CSSValuePool::singleton(); |
| |
| for (auto& watchPropertiesMap : { propertiesA, propertiesB }) { |
| for (auto& name : watchPropertiesMap) { |
| if (isCustomPropertyName(name)) { |
| RefPtr valueA = a.customPropertyValue(name); |
| RefPtr valueB = b.customPropertyValue(name); |
| |
| if (valueA != valueB && (!valueA || !valueB || *valueA != *valueB)) |
| return true; |
| } else if (auto propertyID = cssPropertyID(name)) { |
| auto valueA = extractor.propertyValueInStyle(a, propertyID, pool); |
| auto valueB = extractor.propertyValueInStyle(b, propertyID, pool); |
| |
| if (valueA != valueB && (!valueA || !valueB || *valueA != *valueB)) |
| return true; |
| } |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| static bool changeRequiresRepaint(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>& changedContextSensitiveProperties) |
| { |
| bool currentColorDiffers = a.inheritedData().color != b.inheritedData().color; |
| |
| if (currentColorDiffers || &a.svgData() != &b.svgData()) { |
| if (svgDataChangeRequiresRepaint(a.svgData(), b.svgData(), currentColorDiffers)) |
| return true; |
| } |
| |
| if (!requiresPainting(a) && !requiresPainting(b)) |
| return false; |
| |
| if (a.usedVisibility() != b.usedVisibility() |
| || a.inheritedFlags().printColorAdjust != b.inheritedFlags().printColorAdjust |
| || a.inheritedFlags().insideLink != b.inheritedFlags().insideLink) |
| return true; |
| |
| |
| if (currentColorDiffers || &a.nonInheritedData() != &b.nonInheritedData()) { |
| if (currentColorDiffers || a.nonInheritedData().backgroundData.ptr() != b.nonInheritedData().backgroundData.ptr()) { |
| if (!isEquivalentForPainting(*a.nonInheritedData().backgroundData, *b.nonInheritedData().backgroundData, currentColorDiffers)) |
| return true; |
| } |
| |
| if (currentColorDiffers || a.nonInheritedData().surroundData.ptr() != b.nonInheritedData().surroundData.ptr()) { |
| if (!isEquivalentForPainting(a.nonInheritedData().surroundData->border, b.nonInheritedData().surroundData->border, currentColorDiffers)) |
| return true; |
| } |
| } |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData()) { |
| if (a.nonInheritedData().miscData.ptr() != b.nonInheritedData().miscData.ptr() |
| && miscDataChangeRequiresRepaint(*a.nonInheritedData().miscData, *b.nonInheritedData().miscData, changedContextSensitiveProperties)) |
| return true; |
| |
| if (a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr() |
| && rareDataChangeRequiresRepaint(*a.nonInheritedData().rareData, *b.nonInheritedData().rareData, changedContextSensitiveProperties)) |
| return true; |
| } |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData() |
| && rareInheritedDataChangeRequiresRepaint(a.inheritedRareData(), b.inheritedRareData())) |
| return true; |
| |
| if (changedCustomPaintWatchedProperty(a, *a.nonInheritedData().rareData, b, *b.nonInheritedData().rareData)) |
| return true; |
| |
| return false; |
| } |
| |
| // MARK: DifferenceResult::RepaintIfText |
| |
| static bool changeRequiresRepaintIfText(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>&) |
| { |
| // FIXME: Does this code need to consider currentColorDiffers? webkit.org/b/266833 |
| if (a.inheritedData().color != b.inheritedData().color) |
| return true; |
| |
| // Note that we may reach this function with mutated text-decoration values (e.g. thickness), when visual overflow recompute is not required. |
| // see `changeAffectsVisualOverflow` |
| if (a.inheritedFlags().textDecorationLineInEffect != b.inheritedFlags().textDecorationLineInEffect |
| || a.nonInheritedFlags().textDecorationLine != b.nonInheritedFlags().textDecorationLine) |
| return true; |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData()) { |
| if (a.inheritedRareData().textDecorationSkipInk != b.inheritedRareData().textDecorationSkipInk |
| || a.inheritedRareData().textFillColor != b.inheritedRareData().textFillColor |
| || a.inheritedRareData().textStrokeColor != b.inheritedRareData().textStrokeColor |
| || a.inheritedRareData().textEmphasisColor != b.inheritedRareData().textEmphasisColor |
| || a.inheritedRareData().textEmphasisStyle != b.inheritedRareData().textEmphasisStyle |
| || a.inheritedRareData().strokeColor != b.inheritedRareData().strokeColor |
| || a.inheritedRareData().caretColor != b.inheritedRareData().caretColor |
| || a.inheritedRareData().textUnderlineOffset != b.inheritedRareData().textUnderlineOffset) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // MARK: DifferenceResult::RecompositeLayer |
| |
| static bool changeRequiresRecompositeLayer(const RenderStyle& a, const RenderStyle& b, OptionSet<DifferenceContextSensitiveProperty>&) |
| { |
| if (a.inheritedFlags().pointerEvents != b.inheritedFlags().pointerEvents) |
| return true; |
| |
| if (&a.nonInheritedData() != &b.nonInheritedData() && a.nonInheritedData().rareData.ptr() != b.nonInheritedData().rareData.ptr()) { |
| if (a.usedTransformStyle3D() != b.usedTransformStyle3D() |
| || a.nonInheritedData().rareData->backfaceVisibility != b.nonInheritedData().rareData->backfaceVisibility |
| || a.nonInheritedData().rareData->perspective != b.nonInheritedData().rareData->perspective |
| || a.nonInheritedData().rareData->perspectiveOrigin != b.nonInheritedData().rareData->perspectiveOrigin |
| || a.nonInheritedData().rareData->overscrollBehaviorX != b.nonInheritedData().rareData->overscrollBehaviorX |
| || a.nonInheritedData().rareData->overscrollBehaviorY != b.nonInheritedData().rareData->overscrollBehaviorY) |
| return true; |
| } |
| |
| if (&a.inheritedRareData() != &b.inheritedRareData() && a.inheritedRareData().effectiveInert != b.inheritedRareData().effectiveInert) |
| return true; |
| |
| return false; |
| } |
| |
| // MARK: - Root Functions |
| |
| static bool differenceRequiresLayerRepaint(const RenderStyle& a, const RenderStyle& b, bool isComposited) |
| { |
| auto changedContextSensitiveProperties = OptionSet<DifferenceContextSensitiveProperty>(); |
| |
| if (changeRequiresRepaint(a, b, changedContextSensitiveProperties)) |
| return true; |
| |
| if (isComposited && changeRequiresLayerRepaint(a, b, changedContextSensitiveProperties)) |
| return changedContextSensitiveProperties.contains(DifferenceContextSensitiveProperty::ClipRect); |
| |
| return false; |
| } |
| |
| static bool borderIsEquivalentForPainting(const RenderStyle& a, const RenderStyle& b) |
| { |
| bool colorDiffers = a.color() != b.color(); |
| |
| if (!colorDiffers |
| && (&a.nonInheritedData() == &b.nonInheritedData() |
| || a.nonInheritedData().surroundData.ptr() == b.nonInheritedData().surroundData.ptr() |
| || a.nonInheritedData().surroundData->border == b.nonInheritedData().surroundData->border)) |
| return true; |
| |
| return isEquivalentForPainting(a.border(), b.border(), colorDiffers); |
| } |
| |
| static Difference difference(const RenderStyle& a, const RenderStyle& b) |
| { |
| auto changedContextSensitiveProperties = OptionSet<DifferenceContextSensitiveProperty>(); |
| |
| if (changeRequiresLayout(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::Layout, changedContextSensitiveProperties }; |
| |
| if (changeRequiresOutOfFlowMovementLayoutOnly(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::LayoutOutOfFlowMovementOnly, changedContextSensitiveProperties }; |
| |
| if (changeRequiresLayerRepaint(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::RepaintLayer, changedContextSensitiveProperties }; |
| |
| if (changeRequiresRepaint(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::Repaint, changedContextSensitiveProperties }; |
| |
| if (changeRequiresRepaintIfText(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::RepaintIfText, changedContextSensitiveProperties }; |
| |
| // FIXME: RecompositeLayer should also behave as a priority bit (e.g when the style change requires layout, we know that |
| // the content also needs repaint and it will eventually get repainted, |
| // but a repaint type of change (e.g. color change) does not necessarily trigger recomposition). |
| if (changeRequiresRecompositeLayer(a, b, changedContextSensitiveProperties)) |
| return { DifferenceResult::RecompositeLayer, changedContextSensitiveProperties }; |
| |
| // Cursors are not checked, since they will be set appropriately in response to mouse events, |
| // so they don't need to cause any repaint or layout. |
| |
| // Animations don't need to be checked either. We always set the new style on the RenderObject, so we will get a chance to fire off |
| // the resulting transition properly. |
| return { DifferenceResult::Equal, changedContextSensitiveProperties }; |
| } |
| |
| // MARK: - Logging |
| |
| #if !LOG_DISABLED |
| static void dumpDifferences(TextStream& ts, const RenderStyle& a, const RenderStyle& b) |
| { |
| a.nonInheritedData().dumpDifferences(ts, b.nonInheritedData()); |
| a.nonInheritedFlags().dumpDifferences(ts, b.nonInheritedFlags()); |
| |
| a.inheritedRareData().dumpDifferences(ts, b.inheritedRareData()); |
| a.inheritedData().dumpDifferences(ts, b.inheritedData()); |
| a.inheritedFlags().dumpDifferences(ts, b.inheritedFlags()); |
| |
| a.svgData().dumpDifferences(ts, b.svgData()); |
| } |
| #endif |
| }; |
| |
| // MARK: - Exported Functions |
| |
| Difference difference(const RenderStyle& a, const RenderStyle& b) |
| { |
| return DifferenceFunctions::difference(a, b); |
| } |
| |
| bool differenceRequiresLayerRepaint(const RenderStyle& a, const RenderStyle& b, bool isComposited) |
| { |
| return DifferenceFunctions::differenceRequiresLayerRepaint(a, b, isComposited); |
| } |
| |
| bool borderIsEquivalentForPainting(const RenderStyle& a, const RenderStyle& b) |
| { |
| return DifferenceFunctions::borderIsEquivalentForPainting(a, b); |
| } |
| |
| // MARK: - Logging |
| |
| TextStream& operator<<(TextStream& ts, Difference value) |
| { |
| return ts << "style diff [" << value.result << "] (context sensitive changes " << value.contextSensitiveProperties << ")"; |
| } |
| |
| TextStream& operator<<(TextStream& ts, DifferenceResult value) |
| { |
| switch (value) { |
| case DifferenceResult::Equal: ts << "equal"_s; break; |
| case DifferenceResult::RecompositeLayer: ts << "recomposite layer"_s; break; |
| case DifferenceResult::Repaint: ts << "repaint"_s; break; |
| case DifferenceResult::RepaintIfText: ts << "repaint if text"_s; break; |
| case DifferenceResult::RepaintLayer: ts << "repaint layer"_s; break; |
| case DifferenceResult::LayoutOutOfFlowMovementOnly: ts << "layout positioned movement only"_s; break; |
| case DifferenceResult::Overflow: ts << "overflow"_s; break; |
| case DifferenceResult::OverflowAndOutOfFlowMovement: ts << "overflow and positioned movement"_s; break; |
| case DifferenceResult::Layout: ts << "layout"_s; break; |
| case DifferenceResult::NewStyle: ts << "new style"_s; break; |
| } |
| return ts; |
| } |
| |
| |
| TextStream& operator<<(TextStream& ts, DifferenceContextSensitiveProperty value) |
| { |
| switch (value) { |
| case DifferenceContextSensitiveProperty::Transform: ts << "transform"_s; break; |
| case DifferenceContextSensitiveProperty::Opacity: ts << "opacity"_s; break; |
| case DifferenceContextSensitiveProperty::Filter: ts << "filter"_s; break; |
| case DifferenceContextSensitiveProperty::ClipRect: ts << "clipRect"_s; break; |
| case DifferenceContextSensitiveProperty::ClipPath: ts << "clipPath"_s; break; |
| case DifferenceContextSensitiveProperty::WillChange: ts << "willChange"_s; break; |
| } |
| return ts; |
| } |
| |
| #if !LOG_DISABLED |
| void dumpDifferences(TextStream& ts, const RenderStyle& a, const RenderStyle& b) |
| { |
| return DifferenceFunctions::dumpDifferences(ts, a, b); |
| } |
| |
| #endif |
| |
| } // namespace Style |
| } // namespace WebCore |