| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) |
| * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com) |
| * Copyright (C) 2005-2019 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org> |
| * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org> |
| * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) |
| * Copyright (c) 2011, Code Aurora Forum. All rights reserved. |
| * Copyright (C) Research In Motion Limited 2011. All rights reserved. |
| * Copyright (C) 2012, 2013 Google Inc. All rights reserved. |
| * Copyright (C) 2014, 2020, 2022 Igalia S.L. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "config.h" |
| #include "StyleAdjuster.h" |
| |
| #include "CSSFontSelector.h" |
| #include "DOMTokenList.h" |
| #include "DOMWindow.h" |
| #include "ElementInlines.h" |
| #include "EventNames.h" |
| #include "FrameView.h" |
| #include "HTMLDialogElement.h" |
| #include "HTMLDivElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLMarqueeElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLSlotElement.h" |
| #include "HTMLTableElement.h" |
| #include "HTMLTextAreaElement.h" |
| #include "HTMLVideoElement.h" |
| #include "MathMLElement.h" |
| #include "ModalContainerObserver.h" |
| #include "Page.h" |
| #include "Quirks.h" |
| #include "RenderBox.h" |
| #include "RenderStyle.h" |
| #include "RenderTheme.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "SVGElement.h" |
| #include "SVGGraphicsElement.h" |
| #include "SVGNames.h" |
| #include "SVGURIReference.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "Text.h" |
| #include "WebAnimationTypes.h" |
| #include <wtf/RobinHoodHashSet.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) |
| { |
| // Intrinsic margin value. |
| const int intrinsicMargin = clampToInteger(2 * style.effectiveZoom()); |
| |
| // FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed. |
| // FIXME: Using "hasQuirk" to decide the margin wasn't set is kind of lame. |
| if (style.width().isIntrinsicOrAuto()) { |
| if (style.marginLeft().hasQuirk()) |
| style.setMarginLeft(Length(intrinsicMargin, LengthType::Fixed)); |
| if (style.marginRight().hasQuirk()) |
| style.setMarginRight(Length(intrinsicMargin, LengthType::Fixed)); |
| } |
| |
| if (style.height().isAuto()) { |
| if (style.marginTop().hasQuirk()) |
| style.setMarginTop(Length(intrinsicMargin, LengthType::Fixed)); |
| if (style.marginBottom().hasQuirk()) |
| style.setMarginBottom(Length(intrinsicMargin, LengthType::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::Grid: |
| case DisplayType::FlowRoot: |
| return display; |
| |
| case DisplayType::ListItem: |
| // It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, but only in quirks mode. |
| 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: |
| 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 bool shouldInheritTextDecorationsInEffect(const RenderStyle& style, const Element* element) |
| { |
| if (style.isFloating() || style.hasOutOfFlowPosition()) |
| return false; |
| |
| auto isAtUserAgentShadowBoundary = [&] { |
| if (!element) |
| return false; |
| auto* parentNode = element->parentNode(); |
| return parentNode && parentNode->isUserAgentShadowRoot(); |
| }(); |
| |
| // There is no other good way to prevent decorations from affecting user agent shadow trees. |
| if (isAtUserAgentShadowBoundary) |
| return false; |
| |
| switch (style.display()) { |
| case DisplayType::Table: |
| case DisplayType::InlineTable: |
| case DisplayType::InlineBlock: |
| case DisplayType::InlineBox: |
| return false; |
| default: |
| break; |
| }; |
| |
| return true; |
| } |
| |
| static bool isScrollableOverflow(Overflow overflow) |
| { |
| return overflow == Overflow::Scroll || overflow == Overflow::Auto; |
| } |
| |
| static OptionSet<TouchAction> computeEffectiveTouchActions(const RenderStyle& style, OptionSet<TouchAction> effectiveTouchActions) |
| { |
| // https://w3c.github.io/pointerevents/#determining-supported-touch-behavior |
| // "A touch behavior is supported if it conforms to the touch-action property of each element between |
| // the hit tested element and its nearest ancestor with the default touch behavior (including both the |
| // hit tested element and the element with the default touch behavior)." |
| |
| 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; |
| } |
| |
| void Adjuster::adjustEventListenerRegionTypesForRootStyle(RenderStyle& rootStyle, const Document& document) |
| { |
| auto regionTypes = computeEventListenerRegionTypes(document, rootStyle, document, { }); |
| if (auto* window = document.domWindow()) |
| regionTypes.add(computeEventListenerRegionTypes(document, rootStyle, *window, { })); |
| |
| rootStyle.setEventListenerRegionTypes(regionTypes); |
| } |
| |
| OptionSet<EventListenerRegionType> Adjuster::computeEventListenerRegionTypes(const Document& document, const RenderStyle& style, const EventTarget& eventTarget, OptionSet<EventListenerRegionType> parentTypes) |
| { |
| auto types = parentTypes; |
| |
| #if ENABLE(WHEEL_EVENT_REGIONS) |
| auto findListeners = [&](auto& eventName, auto type, auto nonPassiveType) { |
| auto* eventListenerVector = eventTarget.eventTargetData()->eventListenerMap.find(eventName); |
| if (!eventListenerVector) |
| return; |
| |
| types.add(type); |
| |
| auto isPassiveOnly = [&] { |
| for (auto& listener : *eventListenerVector) { |
| if (!listener->isPassive()) |
| return false; |
| } |
| return true; |
| }(); |
| |
| if (!isPassiveOnly) |
| types.add(nonPassiveType); |
| }; |
| |
| if (eventTarget.hasEventListeners()) { |
| findListeners(eventNames().wheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel); |
| findListeners(eventNames().mousewheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel); |
| } |
| #endif |
| |
| #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION) |
| if (document.page() && document.page()->shouldBuildInteractionRegions() && eventTarget.isNode()) { |
| const auto& node = downcast<Node>(eventTarget); |
| if (node.willRespondToMouseClickEventsWithEditability(node.computeEditabilityForMouseClickEvents(&style))) |
| types.add(EventListenerRegionType::MouseClick); |
| } |
| #else |
| UNUSED_PARAM(document); |
| UNUSED_PARAM(style); |
| #endif |
| |
| #if !ENABLE(WHEEL_EVENT_REGIONS) && !ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION) |
| UNUSED_PARAM(eventTarget); |
| #endif |
| |
| return types; |
| } |
| |
| void Adjuster::adjust(RenderStyle& style, const RenderStyle* userAgentAppearanceStyle) const |
| { |
| if (style.display() == DisplayType::Contents) |
| adjustDisplayContentsStyle(style); |
| |
| if (style.display() != DisplayType::None && style.display() != DisplayType::Contents) { |
| if (m_element) { |
| // If we have a <td> that specifies a float property, in quirks mode we just drop the float |
| // property. |
| // Sites also commonly use display:inline/block on <td>s and <table>s. In quirks mode we force |
| // these tags to retain their display types. |
| if (m_document.inQuirksMode()) { |
| if (m_element->hasTagName(tdTag)) { |
| style.setEffectiveDisplay(DisplayType::TableCell); |
| style.setFloating(Float::None); |
| } else if (is<HTMLTableElement>(*m_element)) |
| style.setEffectiveDisplay(style.isDisplayInlineType() ? DisplayType::InlineTable : DisplayType::Table); |
| } |
| |
| if (m_element->hasTagName(tdTag) || m_element->hasTagName(thTag)) { |
| if (style.whiteSpace() == WhiteSpace::KHTMLNoWrap) { |
| // Figure out if we are really nowrapping or if we should just |
| // use normal instead. If the width of the cell is fixed, then |
| // we don't actually use WhiteSpace::NoWrap. |
| if (style.width().isFixed()) |
| style.setWhiteSpace(WhiteSpace::Normal); |
| else |
| style.setWhiteSpace(WhiteSpace::NoWrap); |
| } |
| } |
| |
| // Tables never support the -webkit-* values for text-align and will reset back to the default. |
| if (is<HTMLTableElement>(*m_element) && (style.textAlign() == TextAlignMode::WebKitLeft || style.textAlign() == TextAlignMode::WebKitCenter || style.textAlign() == TextAlignMode::WebKitRight)) |
| style.setTextAlign(TextAlignMode::Start); |
| |
| // Frames and framesets never honor position:relative or position:absolute. This is necessary to |
| // fix a crash where a site tries to position these objects. They also never honor display. |
| if (m_element->hasTagName(frameTag) || m_element->hasTagName(framesetTag)) { |
| style.setPosition(PositionType::Static); |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| |
| // Ruby text does not support float or position. This might change with evolution of the specification. |
| if (m_element->hasTagName(rtTag)) { |
| style.setPosition(PositionType::Static); |
| style.setFloating(Float::None); |
| } |
| |
| if (m_element->hasTagName(legendTag)) |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| |
| // Top layer elements are always position: absolute; unless the position is set to fixed. |
| // https://fullscreen.spec.whatwg.org/#new-stacking-layer |
| if (style.position() != PositionType::Absolute && style.position() != PositionType::Fixed && isInTopLayerOrBackdrop(style, m_element)) |
| style.setPosition(PositionType::Absolute); |
| |
| // Absolute/fixed positioned elements, floating elements and the document element need block-like outside display. |
| if (style.hasOutOfFlowPosition() || style.isFloating() || (m_element && m_document.documentElement() == m_element)) |
| style.setEffectiveDisplay(equivalentBlockDisplay(style, m_document)); |
| |
| // FIXME: Don't support this mutation for pseudo styles like first-letter or first-line, since it's not completely |
| // clear how that should work. |
| if (style.display() == DisplayType::Inline && style.styleType() == PseudoId::None && style.writingMode() != m_parentStyle.writingMode()) |
| style.setEffectiveDisplay(DisplayType::InlineBlock); |
| |
| // After performing the display mutation, check table rows. We do not honor position:relative or position:sticky on |
| // table rows or cells. This has been established for position:relative in CSS2.1 (and caused a crash in containingBlock() |
| // on some sites). |
| 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); |
| |
| // writing-mode does not apply to table row groups, table column groups, table rows, and table columns. |
| // FIXME: Table cells should be allowed to be perpendicular or flipped with respect to the table, though. |
| 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()); |
| |
| // FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting |
| // of block-flow to anything other than WritingMode::TopToBottom. |
| // https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support. |
| if (style.writingMode() != WritingMode::TopToBottom && (style.display() == DisplayType::Box || style.display() == DisplayType::InlineBox)) |
| style.setWritingMode(WritingMode::TopToBottom); |
| |
| // https://www.w3.org/TR/css-display/#transformations |
| // "A parent with a grid or flex display value blockifies the box’s display type." |
| if (m_parentBoxStyle.isDisplayFlexibleOrGridBox()) { |
| style.setFloating(Float::None); |
| style.setEffectiveDisplay(equivalentBlockDisplay(style, m_document)); |
| } |
| } |
| |
| // Make sure our z-index value is only applied if the object is positioned. |
| if (style.hasAutoSpecifiedZIndex() || (style.position() == PositionType::Static && !m_parentBoxStyle.isDisplayFlexibleOrGridBox())) |
| style.setHasAutoUsedZIndex(); |
| else |
| style.setUsedZIndex(style.specifiedZIndex()); |
| |
| // For SVG compatibility purposes we have to consider the 'animatedLocalTransform' besides the RenderStyle to query |
| // if an element has a transform. SVG transforms are not stored on the RenderStyle, and thus we need a special case here. |
| auto hasTransformRelatedProperty = [](const RenderStyle& style, const Element* element) { |
| if (style.hasTransformRelatedProperty()) |
| return true; |
| |
| #if ENABLE(LAYER_BASED_SVG_ENGINE) |
| if (element && element->document().settings().layerBasedSVGEngineEnabled() && is<SVGGraphicsElement>(element)) |
| return !downcast<SVGGraphicsElement>(*element).animatedLocalTransform().isIdentity(); |
| #else |
| UNUSED_PARAM(element); |
| #endif |
| |
| return false; |
| }; |
| |
| // Auto z-index becomes 0 for the root element and transparent objects. This prevents |
| // cases where objects that should be blended as a single unit end up with a non-transparent |
| // object wedged in between them. Auto z-index also becomes 0 for objects that specify transforms/masks/reflections. |
| if (style.hasAutoUsedZIndex()) { |
| if ((m_element && m_document.documentElement() == m_element) |
| || style.hasOpacity() |
| || hasTransformRelatedProperty(style, m_element) |
| || 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() |
| || isInTopLayerOrBackdrop(style, m_element)) |
| style.setUsedZIndex(0); |
| } |
| |
| if (m_element) { |
| // Textarea considers overflow visible as auto. |
| 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 (is<HTMLInputElement>(*m_element) && downcast<HTMLInputElement>(*m_element).isPasswordField()) |
| style.setTextSecurity(style.inputSecurity() == InputSecurity::Auto ? TextSecurity::Disc : TextSecurity::None); |
| |
| // Disallow -webkit-user-modify on :pseudo and ::pseudo elements. |
| if (!m_element->shadowPseudoId().isNull()) |
| style.setUserModify(UserModify::ReadOnly); |
| |
| if (is<HTMLMarqueeElement>(*m_element)) { |
| // For now, <marquee> requires an overflow clip to work properly. |
| style.setOverflowX(Overflow::Hidden); |
| style.setOverflowY(Overflow::Hidden); |
| |
| bool isVertical = style.marqueeDirection() == MarqueeDirection::Up || style.marqueeDirection() == MarqueeDirection::Down; |
| // Make horizontal marquees not wrap. |
| if (!isVertical) { |
| style.setWhiteSpace(WhiteSpace::NoWrap); |
| style.setTextAlign(TextAlignMode::Start); |
| } |
| // Apparently this is the expected legacy behavior. |
| if (isVertical && style.height().isAuto()) |
| style.setHeight(Length(200, LengthType::Fixed)); |
| } |
| } |
| |
| if (shouldInheritTextDecorationsInEffect(style, m_element)) |
| style.addToTextDecorationsInEffect(style.textDecorationLine()); |
| else |
| style.setTextDecorationsInEffect(style.textDecorationLine()); |
| |
| auto overflowReplacement = [] (Overflow overflow, Overflow overflowInOtherDimension) -> std::optional<Overflow> { |
| if (overflow != Overflow::Visible && overflow != Overflow::Clip) { |
| if (overflowInOtherDimension == Overflow::Visible) |
| return Overflow::Auto; |
| if (overflowInOtherDimension == Overflow::Clip) |
| return Overflow::Hidden; |
| } |
| return std::nullopt; |
| }; |
| |
| // If either overflow value is not visible, change to auto. Similarly if either overflow |
| // value is not clip, change to hidden. |
| // FIXME: Once we implement pagination controls, overflow-x should default to hidden |
| // if overflow-y is set to -webkit-paged-x or -webkit-page-y. For now, we'll let it |
| // default to auto so we can at least scroll through the pages. |
| if (auto replacement = overflowReplacement(style.overflowY(), style.overflowX())) |
| style.setOverflowX(*replacement); |
| else if (auto replacement = overflowReplacement(style.overflowX(), style.overflowY())) |
| style.setOverflowY(*replacement); |
| |
| // Call setStylesForPaginationMode() if a pagination mode is set for any non-root elements. If these |
| // styles are specified on a root element, then they will be incorporated in |
| // Style::createForm_document. |
| if ((style.overflowY() == Overflow::PagedX || style.overflowY() == Overflow::PagedY) && !(m_element && (m_element->hasTagName(htmlTag) || m_element->hasTagName(bodyTag)))) |
| style.setColumnStylesFromPaginationMode(WebCore::paginationModeForRenderStyle(style)); |
| |
| // Table rows, sections and the table itself will support overflow:hidden and will ignore scroll/auto. |
| // FIXME: Eventually table sections will support auto and scroll. |
| 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 ENABLE(OVERFLOW_SCROLLING_TOUCH) |
| // Touch overflow scrolling creates a stacking context. |
| if (style.hasAutoUsedZIndex() && style.useTouchOverflowScrolling() && (isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY()))) |
| style.setUsedZIndex(0); |
| #endif |
| |
| // contain: layout creates a stacking context. |
| if (style.hasAutoUsedZIndex() && style.containsLayout()) |
| style.setUsedZIndex(0); |
| |
| // Cull out any useless layers and also repeat patterns into additional layers. |
| style.adjustBackgroundLayers(); |
| style.adjustMaskLayers(); |
| |
| // Do the same for animations and transitions. |
| style.adjustAnimations(); |
| style.adjustTransitions(); |
| |
| // Important: Intrinsic margins get added to controls before the theme has adjusted the style, since the theme will |
| // alter fonts and heights/widths. |
| if (is<HTMLFormControlElement>(m_element) && style.computedFontPixelSize() >= 11) { |
| // Don't apply intrinsic margins to image buttons. The designer knows how big the images are, |
| // so we have to treat all image buttons as though they were explicitly sized. |
| if (!is<HTMLInputElement>(*m_element) || !downcast<HTMLInputElement>(*m_element).isImageButton()) |
| addIntrinsicMargins(style); |
| } |
| |
| // Let the theme also have a crack at adjusting the style. |
| if (style.hasAppearance()) |
| RenderTheme::singleton().adjustStyle(style, m_element, userAgentAppearanceStyle); |
| |
| // If we have first-letter pseudo style, do not share this style. |
| if (style.hasPseudoStyle(PseudoId::FirstLetter)) |
| style.setUnique(); |
| |
| if (style.preserves3D()) { |
| bool forceToFlat = style.overflowX() != Overflow::Visible |
| || style.hasOpacity() |
| || style.overflowY() != Overflow::Visible |
| || style.hasClip() |
| || style.clipPath() |
| || style.hasFilter() |
| || style.hasIsolation() |
| || style.hasMask() |
| #if ENABLE(FILTERS_LEVEL_2) |
| || style.hasBackdropFilter() |
| #endif |
| || style.hasBlendMode(); |
| style.setTransformStyleForcedToFlat(forceToFlat); |
| } |
| |
| if (is<SVGElement>(m_element)) |
| adjustSVGElementStyle(style, downcast<SVGElement>(*m_element)); |
| |
| // If the inherited value of justify-items includes the 'legacy' keyword (plus 'left', 'right' or |
| // 'center'), 'legacy' computes to the the inherited value. Otherwise, 'auto' computes to 'normal'. |
| if (m_parentBoxStyle.justifyItems().positionType() == ItemPositionType::Legacy && style.justifyItems().position() == ItemPosition::Legacy) |
| style.setJustifyItems(m_parentBoxStyle.justifyItems()); |
| |
| style.setEffectiveTouchActions(computeEffectiveTouchActions(style, m_parentStyle.effectiveTouchActions())); |
| |
| // Counterparts in Element::addToTopLayer/removeFromTopLayer & SharingResolver::canShareStyleWithElement need to match! |
| auto hasInertAttribute = [this] (const Element* element) -> bool { |
| return m_document.settings().inertAttributeEnabled() && is<HTMLElement>(element) && element->hasAttributeWithoutSynchronization(HTMLNames::inertAttr); |
| }; |
| auto isInertSubtreeRoot = [this, hasInertAttribute] (const Element* element) -> bool { |
| if (m_document.activeModalDialog() && element == m_document.documentElement()) |
| return true; |
| if (hasInertAttribute(element)) |
| return true; |
| return false; |
| }; |
| if (isInertSubtreeRoot(m_element)) |
| style.setEffectiveInert(true); |
| |
| if (m_element) { |
| // Make sure the active dialog is interactable when the whole document is blocked by the modal dialog |
| if (m_element == m_document.activeModalDialog() && !hasInertAttribute(m_element)) |
| style.setEffectiveInert(false); |
| |
| style.setEventListenerRegionTypes(computeEventListenerRegionTypes(m_document, style, *m_element, m_parentStyle.eventListenerRegionTypes())); |
| |
| #if ENABLE(TEXT_AUTOSIZING) |
| if (m_document.settings().textAutosizingUsesIdempotentMode()) |
| adjustForTextAutosizing(style, *m_element); |
| #endif |
| |
| if (auto observer = m_element->document().modalContainerObserverIfExists()) { |
| if (observer->shouldHide(*m_element)) |
| style.setDisplay(DisplayType::None); |
| if (observer->shouldMakeVerticallyScrollable(*m_element)) |
| style.setOverflowY(Overflow::Auto); |
| } |
| } |
| |
| adjustForSiteSpecificQuirks(style); |
| } |
| |
| static bool hasEffectiveDisplayNoneForDisplayContents(const Element& element) |
| { |
| // https://drafts.csswg.org/css-display-3/#unbox-html |
| static NeverDestroyed<MemoryCompactLookupOnlyRobinHoodHashSet<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(), |
| }; |
| MemoryCompactLookupOnlyRobinHoodHashSet<AtomString> set; |
| set.reserveInitialCapacity(sizeof(tagList)); |
| for (auto& name : tagList) |
| set.add(name->localName()); |
| return set; |
| }(); |
| |
| // https://drafts.csswg.org/css-display-3/#unbox-svg |
| // FIXME: <g>, <use> and <tspan> have special (?) behavior for display:contents in the current draft spec. |
| if (is<SVGElement>(element)) |
| return true; |
| #if ENABLE(MATHML) |
| // Not sure MathML code can handle it. |
| 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 |
| { |
| bool isInTopLayer = isInTopLayerOrBackdrop(style, m_element); |
| if (isInTopLayer || m_document.documentElement() == m_element) { |
| style.setEffectiveDisplay(DisplayType::Block); |
| return; |
| } |
| |
| if (!m_element && style.styleType() != PseudoId::Before && style.styleType() != PseudoId::After) { |
| style.setEffectiveDisplay(DisplayType::None); |
| return; |
| } |
| |
| if (m_element && hasEffectiveDisplayNoneForDisplayContents(*m_element)) |
| style.setEffectiveDisplay(DisplayType::None); |
| } |
| |
| void Adjuster::adjustSVGElementStyle(RenderStyle& style, const SVGElement& svgElement) |
| { |
| // Only the root <svg> element in an SVG document fragment tree honors css position |
| auto isPositioningAllowed = svgElement.hasTagName(SVGNames::svgTag) && svgElement.parentNode() && !svgElement.parentNode()->isSVGElement() && !svgElement.correspondingElement(); |
| if (!isPositioningAllowed) |
| style.setPosition(RenderStyle::initialPosition()); |
| |
| #if ENABLE(LAYER_BASED_SVG_ENGINE) |
| // SVG2: A new stacking context must be established at an SVG element for its descendants if: |
| // - it is the root element |
| // - the "z-index" property applies to the element and its computed value is an integer |
| // - the element is an outermost svg element, or a "foreignObject", "image", "marker", "mask", "pattern", "symbol" or "use" element |
| // - the element is an inner "svg" element and the computed value of its "overflow" property is a value other than visible |
| // - the element is subject to explicit clipping: |
| // - the "clip" property applies to the element and it has a computed value other than auto |
| // - the "clip-path" property applies to the element and it has a computed value other than none |
| // - the "mask" property applies to the element and it has a computed value other than none |
| // - the "filter" property applies to the element and it has a computed value other than none |
| // - a property defined in another specification is applied and that property is defined to establish a stacking context in SVG |
| // |
| // Some of the rules above were already enforced in StyleResolver::adjustRenderStyle() - for those cases assertions were added. |
| if (svgElement.document().settings().layerBasedSVGEngineEnabled() && style.hasAutoUsedZIndex()) { |
| // adjustRenderStyle() has already assigned a z-index of 0 if clip / filter is present or the element is the root element. |
| ASSERT(!style.hasClip()); |
| ASSERT(!style.clipPath()); |
| ASSERT(!style.hasFilter()); |
| ASSERT(!svgElement.isOutermostSVGSVGElement()); |
| |
| auto isInnerSVGElement = [] (const SVGElement& svgElement) -> bool { |
| return svgElement.hasTagName(SVGNames::svgTag) && svgElement.parentNode() && is<SVGElement>(svgElement.parentNode()); |
| }; |
| |
| if (svgElement.hasTagName(SVGNames::foreignObjectTag) |
| || svgElement.hasTagName(SVGNames::imageTag) |
| || svgElement.hasTagName(SVGNames::markerTag) |
| || svgElement.hasTagName(SVGNames::maskTag) |
| || svgElement.hasTagName(SVGNames::patternTag) |
| || svgElement.hasTagName(SVGNames::symbolTag) |
| || svgElement.hasTagName(SVGNames::useTag) |
| || (isInnerSVGElement(svgElement) && (style.overflowX() != Overflow::Visible || style.overflowY() != Overflow::Visible)) |
| || style.hasPositionedMask()) |
| style.setUsedZIndex(0); |
| } |
| #endif |
| |
| // (Legacy)RenderSVGRoot handles zooming for the whole SVG subtree, so foreignObject content should |
| // not be scaled again. |
| if (svgElement.hasTagName(SVGNames::foreignObjectTag)) |
| style.setEffectiveZoom(RenderStyle::initialZoom()); |
| |
| // SVG text layout code expects us to be a block-level style element. |
| if ((svgElement.hasTagName(SVGNames::foreignObjectTag) || svgElement.hasTagName(SVGNames::textTag)) && style.isDisplayInlineType()) |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| |
| void Adjuster::adjustAnimatedStyle(RenderStyle& style, OptionSet<AnimationImpact> impact) const |
| { |
| adjust(style, nullptr); |
| |
| // Set an explicit used z-index in two cases: |
| // 1. When the element respects z-index, and the style has an explicit z-index set (for example, the animation |
| // itself may animate z-index). |
| // 2. When we want the stacking context side-effets of explicit z-index, via forceStackingContext. |
| // It's important to not clobber an existing used z-index, since an earlier animation may have set it, but we |
| // may still need to update the used z-index value from the specified value. |
| |
| if (style.hasAutoUsedZIndex() && impact.contains(AnimationImpact::ForcesStackingContext)) |
| style.setUsedZIndex(0); |
| } |
| |
| void Adjuster::adjustForSiteSpecificQuirks(RenderStyle& style) const |
| { |
| if (!m_element) |
| return; |
| |
| if (m_document.quirks().needsGMailOverflowScrollQuirk()) { |
| // This turns sidebar scrollable without mouse move event. |
| static MainThreadNeverDestroyed<const AtomString> roleValue("navigation"_s); |
| if (style.overflowY() == Overflow::Hidden && m_element->attributeWithoutSynchronization(roleAttr) == roleValue) |
| style.setOverflowY(Overflow::Auto); |
| } |
| if (m_document.quirks().needsYouTubeOverflowScrollQuirk()) { |
| // This turns sidebar scrollable without hover. |
| static MainThreadNeverDestroyed<const AtomString> idValue("guide-inner-content"_s); |
| if (style.overflowY() == Overflow::Hidden && m_element->idForStyleResolution() == idValue) |
| style.setOverflowY(Overflow::Auto); |
| } |
| if (m_document.quirks().needsWeChatScrollingQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> class1("tree-select"_s); |
| static MainThreadNeverDestroyed<const AtomString> class2("v-tree-select"_s); |
| const auto& flexBasis = style.flexBasis(); |
| if (style.minHeight().isAuto() |
| && style.display() == DisplayType::Flex |
| && style.flexGrow() == 1 |
| && style.flexShrink() == 1 |
| && (flexBasis.isPercent() || flexBasis.isFixed()) |
| && flexBasis.value() == 0 |
| && const_cast<Element*>(m_element)->classList().contains(class1) |
| && const_cast<Element*>(m_element)->classList().contains(class2)) |
| style.setMinHeight(Length(0, LengthType::Fixed)); |
| } |
| #if ENABLE(VIDEO) |
| if (m_document.quirks().needsFullscreenDisplayNoneQuirk()) { |
| if (is<HTMLDivElement>(m_element) && style.display() == DisplayType::None) { |
| static MainThreadNeverDestroyed<const AtomString> instreamNativeVideoDivClass("instream-native-video--mobile"_s); |
| static MainThreadNeverDestroyed<const AtomString> videoElementID("vjs_video_3_html5_api"_s); |
| |
| auto& div = downcast<HTMLDivElement>(*m_element); |
| if (div.hasClass() && div.classNames().contains(instreamNativeVideoDivClass)) { |
| auto* video = div.treeScope().getElementById(videoElementID); |
| if (is<HTMLVideoElement>(video) && downcast<HTMLVideoElement>(*video).isFullscreen()) |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| } |
| } |
| #endif |
| } |
| |
| #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; |
| } |
| |
| auto Adjuster::adjustmentForTextAutosizing(const RenderStyle& style, const Element& element) -> AdjustmentForTextAutosizing |
| { |
| AdjustmentForTextAutosizing adjustmentForTextAutosizing; |
| |
| auto& document = element.document(); |
| if (!document.settings().textAutosizingEnabled() |
| || !document.settings().textAutosizingUsesIdempotentMode() |
| || document.settings().idempotentModeAutosizingOnlyHonorsPercentages()) |
| return adjustmentForTextAutosizing; |
| |
| auto newStatus = AutosizeStatus::computeStatus(style); |
| if (newStatus != style.autosizeStatus()) |
| adjustmentForTextAutosizing.newStatus = newStatus; |
| |
| if (style.textSizeAdjust().isNone()) |
| return adjustmentForTextAutosizing; |
| |
| float initialScale = document.page() ? document.page()->initialScaleIgnoringContentSize() : 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; |
| |
| adjustmentForTextAutosizing.newLineHeight = minimumLineHeight; |
| }; |
| |
| auto fontDescription = style.fontDescription(); |
| auto initialComputedFontSize = fontDescription.computedSize(); |
| auto specifiedFontSize = fontDescription.specifiedSize(); |
| bool isCandidate = style.isIdempotentTextAutosizingCandidate(newStatus); |
| if (!isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, specifiedFontSize)) |
| return adjustmentForTextAutosizing; |
| |
| auto adjustedFontSize = AutosizeStatus::idempotentTextSize(fontDescription.specifiedSize(), initialScale); |
| if (isCandidate && WTF::areEssentiallyEqual(initialComputedFontSize, adjustedFontSize)) |
| return adjustmentForTextAutosizing; |
| |
| if (!hasTextChild(element)) |
| return adjustmentForTextAutosizing; |
| |
| adjustmentForTextAutosizing.newFontSize = isCandidate ? adjustedFontSize : specifiedFontSize; |
| |
| // FIXME: We should restore computed line height to its original value in the case where the element is not |
| // an idempotent text autosizing candidate; otherwise, if an element that is a text autosizing candidate contains |
| // children which are not autosized, the non-autosized content will end up with a boosted line height. |
| if (isCandidate) |
| adjustLineHeightIfNeeded(adjustedFontSize); |
| |
| return adjustmentForTextAutosizing; |
| } |
| |
| bool Adjuster::adjustForTextAutosizing(RenderStyle& style, const Element& element, AdjustmentForTextAutosizing adjustment) |
| { |
| AutosizeStatus::updateStatus(style); |
| if (auto newFontSize = adjustment.newFontSize) { |
| auto fontDescription = style.fontDescription(); |
| fontDescription.setComputedSize(*newFontSize); |
| style.setFontDescription(WTFMove(fontDescription)); |
| style.fontCascade().update(&element.document().fontSelector()); |
| } |
| if (auto newLineHeight = adjustment.newLineHeight) |
| style.setLineHeight({ *newLineHeight, LengthType::Fixed }); |
| if (auto newStatus = adjustment.newStatus) |
| style.setAutosizeStatus(*newStatus); |
| return adjustment.newFontSize || adjustment.newLineHeight; |
| } |
| |
| bool Adjuster::adjustForTextAutosizing(RenderStyle& style, const Element& element) |
| { |
| return adjustForTextAutosizing(style, element, adjustmentForTextAutosizing(style, element)); |
| } |
| #endif |
| |
| } |
| } |