#include "config.h"
#include "StyleAdjuster.h"
#include "AnimationBase.h"
#include "CSSFontSelector.h"
#include "Element.h"
#include "FrameView.h"
#include "HTMLInputElement.h"
#include "HTMLMarqueeElement.h"
#include "HTMLNames.h"
#include "HTMLSlotElement.h"
#include "HTMLTableElement.h"
#include "HTMLTextAreaElement.h"
#include "MathMLElement.h"
#include "Page.h"
#include "Quirks.h"
#include "RenderBox.h"
#include "RenderStyle.h"
#include "RenderTheme.h"
#include "RuntimeEnabledFeatures.h"
#include "SVGDocument.h"
#include "SVGElement.h"
#include "SVGNames.h"
#include "SVGURIReference.h"
#include "Settings.h"
#include "Text.h"
namespace WebCore {
namespace Style {
using namespace HTMLNames;
Adjuster::Adjuster(const Document& document, const RenderStyle& parentStyle, const RenderStyle* parentBoxStyle, const Element* element)
: m_document(document)
, m_parentStyle(parentStyle)
, m_parentBoxStyle(parentBoxStyle ? *parentBoxStyle : m_parentStyle)
, m_element(element)
{
}
static void addIntrinsicMargins(RenderStyle& style)
{
const int intrinsicMargin = clampToInteger(2 * style.effectiveZoom());
if (style.width().isIntrinsicOrAuto()) {
if (style.marginLeft().hasQuirk())
style.setMarginLeft(Length(intrinsicMargin, Fixed));
if (style.marginRight().hasQuirk())
style.setMarginRight(Length(intrinsicMargin, Fixed));
}
if (style.height().isAuto()) {
if (style.marginTop().hasQuirk())
style.setMarginTop(Length(intrinsicMargin, Fixed));
if (style.marginBottom().hasQuirk())
style.setMarginBottom(Length(intrinsicMargin, Fixed));
}
}
static DisplayType equivalentBlockDisplay(const RenderStyle& style, const Document& document)
{
switch (auto display = style.display()) {
case DisplayType::Block:
case DisplayType::Table:
case DisplayType::Box:
case DisplayType::Flex:
case DisplayType::WebKitFlex:
case DisplayType::Grid:
case DisplayType::FlowRoot:
return display;
case DisplayType::ListItem:
if (document.inQuirksMode() && style.isFloating())
return DisplayType::Block;
return display;
case DisplayType::InlineTable:
return DisplayType::Table;
case DisplayType::InlineBox:
return DisplayType::Box;
case DisplayType::InlineFlex:
case DisplayType::WebKitInlineFlex:
return DisplayType::Flex;
case DisplayType::InlineGrid:
return DisplayType::Grid;
case DisplayType::Inline:
case DisplayType::InlineBlock:
case DisplayType::TableRowGroup:
case DisplayType::TableHeaderGroup:
case DisplayType::TableFooterGroup:
case DisplayType::TableRow:
case DisplayType::TableColumnGroup:
case DisplayType::TableColumn:
case DisplayType::TableCell:
case DisplayType::TableCaption:
return DisplayType::Block;
case DisplayType::Contents:
ASSERT_NOT_REACHED();
return DisplayType::Contents;
case DisplayType::None:
ASSERT_NOT_REACHED();
return DisplayType::None;
}
ASSERT_NOT_REACHED();
return DisplayType::Block;
}
static inline bool isAtShadowBoundary(const Element& element)
{
auto* parentNode = element.parentNode();
return parentNode && parentNode->isShadowRoot();
}
static bool doesNotInheritTextDecoration(const RenderStyle& style, const Element* element)
{
return style.display() == DisplayType::Table || style.display() == DisplayType::InlineTable
|| style.display() == DisplayType::InlineBlock || style.display() == DisplayType::InlineBox || (element && isAtShadowBoundary(*element))
|| style.isFloating() || style.hasOutOfFlowPosition();
}
#if ENABLE(OVERFLOW_SCROLLING_TOUCH) || ENABLE(POINTER_EVENTS)
static bool isScrollableOverflow(Overflow overflow)
{
return overflow == Overflow::Scroll || overflow == Overflow::Auto;
}
#endif
#if ENABLE(POINTER_EVENTS)
static OptionSet<TouchAction> computeEffectiveTouchActions(const RenderStyle& style, OptionSet<TouchAction> effectiveTouchActions)
{
bool hasDefaultTouchBehavior = isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY());
if (hasDefaultTouchBehavior)
effectiveTouchActions = RenderStyle::initialTouchActions();
auto touchActions = style.touchActions();
if (touchActions == RenderStyle::initialTouchActions())
return effectiveTouchActions;
if (effectiveTouchActions.contains(TouchAction::None))
return { TouchAction::None };
if (effectiveTouchActions.containsAny({ TouchAction::Auto, TouchAction::Manipulation }))
return touchActions;
if (touchActions.containsAny({ TouchAction::Auto, TouchAction::Manipulation }))
return effectiveTouchActions;
auto sharedTouchActions = effectiveTouchActions & touchActions;
if (sharedTouchActions.isEmpty())
return { TouchAction::None };
return sharedTouchActions;
}
#endif
void Adjuster::adjust(RenderStyle& style, const RenderStyle* userAgentAppearanceStyle) const
{
style.setOriginalDisplay(style.display());
if (style.display() == DisplayType::Contents)
adjustDisplayContentsStyle(style);
if (style.display() != DisplayType::None && style.display() != DisplayType::Contents) {
if (m_element) {
if (m_document.inQuirksMode()) {
if (m_element->hasTagName(tdTag)) {
style.setDisplay(DisplayType::TableCell);
style.setFloating(Float::No);
} else if (is<HTMLTableElement>(*m_element))
style.setDisplay(style.isDisplayInlineType() ? DisplayType::InlineTable : DisplayType::Table);
}
if (m_element->hasTagName(tdTag) || m_element->hasTagName(thTag)) {
if (style.whiteSpace() == WhiteSpace::KHTMLNoWrap) {
if (style.width().isFixed())
style.setWhiteSpace(WhiteSpace::Normal);
else
style.setWhiteSpace(WhiteSpace::NoWrap);
}
}
if (is<HTMLTableElement>(*m_element) && (style.textAlign() == TextAlignMode::WebKitLeft || style.textAlign() == TextAlignMode::WebKitCenter || style.textAlign() == TextAlignMode::WebKitRight))
style.setTextAlign(TextAlignMode::Start);
if (m_element->hasTagName(frameTag) || m_element->hasTagName(framesetTag)) {
style.setPosition(PositionType::Static);
style.setDisplay(DisplayType::Block);
}
if (m_element->hasTagName(rtTag)) {
style.setPosition(PositionType::Static);
style.setFloating(Float::No);
}
if (m_element->hasTagName(thTag) && !style.hasExplicitlySetTextAlign() && m_parentStyle.textAlign() == RenderStyle::initialTextAlign())
style.setTextAlign(TextAlignMode::Center);
if (m_element->hasTagName(legendTag))
style.setDisplay(DisplayType::Block);
}
if (style.hasOutOfFlowPosition() || style.isFloating() || (m_element && m_document.documentElement() == m_element))
style.setDisplay(equivalentBlockDisplay(style, m_document));
if (style.display() == DisplayType::Inline && style.styleType() == PseudoId::None && style.writingMode() != m_parentStyle.writingMode())
style.setDisplay(DisplayType::InlineBlock);
if ((style.display() == DisplayType::TableHeaderGroup || style.display() == DisplayType::TableRowGroup
|| style.display() == DisplayType::TableFooterGroup || style.display() == DisplayType::TableRow)
&& style.position() == PositionType::Relative)
style.setPosition(PositionType::Static);
if (style.display() == DisplayType::TableColumn || style.display() == DisplayType::TableColumnGroup || style.display() == DisplayType::TableFooterGroup
|| style.display() == DisplayType::TableHeaderGroup || style.display() == DisplayType::TableRow || style.display() == DisplayType::TableRowGroup
|| style.display() == DisplayType::TableCell)
style.setWritingMode(m_parentStyle.writingMode());
if (style.writingMode() != TopToBottomWritingMode && (style.display() == DisplayType::Box || style.display() == DisplayType::InlineBox))
style.setWritingMode(TopToBottomWritingMode);
if (m_parentBoxStyle.isDisplayFlexibleOrGridBox()) {
style.setFloating(Float::No);
style.setDisplay(equivalentBlockDisplay(style, m_document));
}
}
if (style.hasAutoSpecifiedZIndex() || (style.position() == PositionType::Static && !m_parentBoxStyle.isDisplayFlexibleOrGridBox()))
style.setHasAutoUsedZIndex();
else
style.setUsedZIndex(style.specifiedZIndex());
if (style.hasAutoUsedZIndex()) {
if ((m_element && m_document.documentElement() == m_element)
|| style.opacity() < 1.0f
|| style.hasTransformRelatedProperty()
|| style.hasMask()
|| style.clipPath()
|| style.boxReflect()
|| style.hasFilter()
#if ENABLE(FILTERS_LEVEL_2)
|| style.hasBackdropFilter()
#endif
|| style.hasBlendMode()
|| style.hasIsolation()
|| style.position() == PositionType::Sticky
|| style.position() == PositionType::Fixed
|| style.willChangeCreatesStackingContext())
style.setUsedZIndex(0);
}
if (m_element) {
if (is<HTMLTextAreaElement>(*m_element)) {
style.setOverflowX(style.overflowX() == Overflow::Visible ? Overflow::Auto : style.overflowX());
style.setOverflowY(style.overflowY() == Overflow::Visible ? Overflow::Auto : style.overflowY());
}
if (!m_element->shadowPseudoId().isNull())
style.setUserModify(UserModify::ReadOnly);
if (is<HTMLMarqueeElement>(*m_element)) {
style.setOverflowX(Overflow::Hidden);
style.setOverflowY(Overflow::Hidden);
bool isVertical = style.marqueeDirection() == MarqueeDirection::Up || style.marqueeDirection() == MarqueeDirection::Down;
if (!isVertical) {
style.setWhiteSpace(WhiteSpace::NoWrap);
style.setTextAlign(TextAlignMode::Start);
}
if (isVertical && style.height().isAuto())
style.setHeight(Length(200, Fixed));
}
}
if (doesNotInheritTextDecoration(style, m_element))
style.setTextDecorationsInEffect(style.textDecoration());
else
style.addToTextDecorationsInEffect(style.textDecoration());
if (style.overflowX() == Overflow::Visible && style.overflowY() != Overflow::Visible) {
style.setOverflowX(Overflow::Auto);
} else if (style.overflowY() == Overflow::Visible && style.overflowX() != Overflow::Visible)
style.setOverflowY(Overflow::Auto);
if ((style.overflowY() == Overflow::PagedX || style.overflowY() == Overflow::PagedY) && !(m_element && (m_element->hasTagName(htmlTag) || m_element->hasTagName(bodyTag))))
style.setColumnStylesFromPaginationMode(WebCore::paginationModeForRenderStyle(style));
if (style.display() == DisplayType::Table || style.display() == DisplayType::InlineTable
|| style.display() == DisplayType::TableRowGroup || style.display() == DisplayType::TableRow) {
if (style.overflowX() != Overflow::Visible && style.overflowX() != Overflow::Hidden)
style.setOverflowX(Overflow::Visible);
if (style.overflowY() != Overflow::Visible && style.overflowY() != Overflow::Hidden)
style.setOverflowY(Overflow::Visible);
}
if (style.appearance() == MenulistPart) {
style.setOverflowX(Overflow::Visible);
style.setOverflowY(Overflow::Visible);
}
#if ENABLE(OVERFLOW_SCROLLING_TOUCH)
if (style.hasAutoUsedZIndex() && style.useTouchOverflowScrolling() && (isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY())))
style.setUsedZIndex(0);
#endif
style.adjustBackgroundLayers();
style.adjustMaskLayers();
style.adjustAnimations();
style.adjustTransitions();
if (is<HTMLFormControlElement>(m_element) && style.computedFontPixelSize() >= 11) {
if (!is<HTMLInputElement>(*m_element) || !downcast<HTMLInputElement>(*m_element).isImageButton())
addIntrinsicMargins(style);
}
if (style.hasAppearance())
RenderTheme::singleton().adjustStyle(style, m_element, userAgentAppearanceStyle);
if (style.hasPseudoStyle(PseudoId::FirstLetter))
style.setUnique();
if (style.preserves3D() && (style.overflowX() != Overflow::Visible
|| style.overflowY() != Overflow::Visible
|| style.hasClip()
|| style.clipPath()
|| style.hasFilter()
#if ENABLE(FILTERS_LEVEL_2)
|| style.hasBackdropFilter()
#endif
|| style.hasBlendMode()))
style.setTransformStyle3D(TransformStyle3D::Flat);
if (is<SVGElement>(m_element))
adjustSVGElementStyle(style, downcast<SVGElement>(*m_element));
if (m_parentBoxStyle.justifyItems().positionType() == ItemPositionType::Legacy && style.justifyItems().position() == ItemPosition::Legacy)
style.setJustifyItems(m_parentBoxStyle.justifyItems());
#if ENABLE(POINTER_EVENTS)
style.setEffectiveTouchActions(computeEffectiveTouchActions(style, m_parentStyle.effectiveTouchActions()));
#endif
#if ENABLE(TEXT_AUTOSIZING)
if (m_element)
adjustForTextAutosizing(style, *m_element);
#endif
adjustForSiteSpecificQuirks(style);
}
static bool hasEffectiveDisplayNoneForDisplayContents(const Element& element)
{
static NeverDestroyed<HashSet<AtomString>> tagNames = [] {
static const HTMLQualifiedName* const tagList[] = {
&brTag.get(),
&wbrTag.get(),
&meterTag.get(),
&appletTag.get(),
&progressTag.get(),
&canvasTag.get(),
&embedTag.get(),
&objectTag.get(),
&audioTag.get(),
&iframeTag.get(),
&imgTag.get(),
&videoTag.get(),
&frameTag.get(),
&framesetTag.get(),
&inputTag.get(),
&textareaTag.get(),
&selectTag.get(),
};
HashSet<AtomString> set;
for (auto& name : tagList)
set.add(name->localName());
return set;
}();
if (is<SVGElement>(element))
return true;
#if ENABLE(MATHML)
if (is<MathMLElement>(element))
return true;
#endif // ENABLE(MATHML)
if (!is<HTMLElement>(element))
return false;
return tagNames.get().contains(element.localName());
}
void Adjuster::adjustDisplayContentsStyle(RenderStyle& style) const
{
if (!m_element) {
if (style.styleType() != PseudoId::Before && style.styleType() != PseudoId::After)
style.setDisplay(DisplayType::None);
return;
}
if (m_document.documentElement() == m_element) {
style.setDisplay(DisplayType::Block);
return;
}
if (hasEffectiveDisplayNoneForDisplayContents(*m_element))
style.setDisplay(DisplayType::None);
}
void Adjuster::adjustSVGElementStyle(RenderStyle& style, const SVGElement& svgElement)
{
auto isPositioningAllowed = svgElement.hasTagName(SVGNames::svgTag) && svgElement.parentNode() && !svgElement.parentNode()->isSVGElement() && !svgElement.correspondingElement();
if (!isPositioningAllowed)
style.setPosition(RenderStyle::initialPosition());
if (svgElement.hasTagName(SVGNames::foreignObjectTag))
style.setEffectiveZoom(RenderStyle::initialZoom());
if ((svgElement.hasTagName(SVGNames::foreignObjectTag) || svgElement.hasTagName(SVGNames::textTag)) && style.isDisplayInlineType())
style.setDisplay(DisplayType::Block);
}
void Adjuster::adjustAnimatedStyle(RenderStyle& style, const RenderStyle* parentBoxStyle, OptionSet<AnimationImpact> impact)
{
bool elementRespectsZIndex = style.position() != PositionType::Static || (parentBoxStyle && parentBoxStyle->isDisplayFlexibleOrGridBox());
if (elementRespectsZIndex && !style.hasAutoSpecifiedZIndex())
style.setUsedZIndex(style.specifiedZIndex());
else if (impact.contains(AnimationImpact::ForcesStackingContext))
style.setUsedZIndex(0);
}
void Adjuster::adjustForSiteSpecificQuirks(RenderStyle& style) const
{
if (m_document.quirks().needsGMailOverflowScrollQuirk() && m_element) {
static NeverDestroyed<AtomString> roleValue("navigation", AtomString::ConstructFromLiteral);
if (style.overflowY() == Overflow::Hidden && m_element->attributeWithoutSynchronization(roleAttr) == roleValue)
style.setOverflowY(Overflow::Auto);
}
if (m_document.quirks().needsYouTubeOverflowScrollQuirk() && m_element) {
static NeverDestroyed<AtomString> idValue("guide-inner-content", AtomString::ConstructFromLiteral);
if (style.overflowY() == Overflow::Hidden && m_element->idForStyleResolution() == idValue)
style.setOverflowY(Overflow::Auto);
}
}
#if ENABLE(TEXT_AUTOSIZING)
static bool hasTextChild(const Element& element)
{
for (auto* child = element.firstChild(); child; child = child->nextSibling()) {
if (is<Text>(child))
return true;
}
return false;
}
bool Adjuster::adjustForTextAutosizing(RenderStyle& style, const Element& element)
{
auto& document = element.document();
if (!document.settings().textAutosizingEnabled() || !document.settings().textAutosizingUsesIdempotentMode())
return false;
AutosizeStatus::updateStatus(style);
if (style.textSizeAdjust().isNone())
return false;
float initialScale = document.page() ? document.page()->initialScale() : 1;
auto adjustLineHeightIfNeeded = [&](auto computedFontSize) {
auto lineHeight = style.specifiedLineHeight();
constexpr static unsigned eligibleFontSize = 12;
if (computedFontSize * initialScale >= eligibleFontSize)
return;
constexpr static float boostFactor = 1.25;
auto minimumLineHeight = boostFactor * computedFontSize;
if (!lineHeight.isFixed() || lineHeight.value() >= minimumLineHeight)
return;
if (AutosizeStatus::probablyContainsASmallFixedNumberOfLines(style))
return;
style.setLineHeight({ minimumLineHeight, Fixed });
};
auto fontDescription = style.fontDescription();
auto initialComputedFontSize = fontDescription.computedSize();
auto specifiedFontSize = fontDescription.specifiedSize();
bool isCandidate = style.isIdempotentTextAutosizingCandidate();
if (!isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, specifiedFontSize))
return false;
auto adjustedFontSize = AutosizeStatus::idempotentTextSize(fontDescription.specifiedSize(), initialScale);
if (isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, adjustedFontSize))
return false;
if (!hasTextChild(element))
return false;
fontDescription.setComputedSize(isCandidate ? adjustedFontSize : specifiedFontSize);
style.setFontDescription(WTFMove(fontDescription));
style.fontCascade().update(&document.fontSelector());
if (isCandidate)
adjustLineHeightIfNeeded(adjustedFontSize);
return true;
}
#endif
}
}