| /* |
| * Copyright (C) 1999 Lars Knoll ([email protected]) |
| * Copyright (C) 2004-2005 Allan Sandfeld Jensen ([email protected]) |
| * Copyright (C) 2006, 2007 Nicholas Shanks ([email protected]) |
| * Copyright (C) 2005-2025 Apple Inc. All rights reserved. |
| * Copyright (C) 2007 Alexey Proskuryakov <[email protected]> |
| * Copyright (C) 2007, 2008 Eric Seidel <[email protected]> |
| * 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-2020 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 "ContainerNodeInlines.h" |
| #include "DocumentPage.h" |
| #include "DocumentQuirks.h" |
| #include "DocumentView.h" |
| #include "ElementAncestorIterator.h" |
| #include "ElementAncestorIteratorInlines.h" |
| #include "ElementInlines.h" |
| #include "EventNames.h" |
| #include "EventTargetInlines.h" |
| #include "HTMLBodyElement.h" |
| #include "HTMLDialogElement.h" |
| #include "HTMLDivElement.h" |
| #include "HTMLInputElement.h" |
| #include "HTMLLabelElement.h" |
| #include "HTMLMarqueeElement.h" |
| #include "HTMLNames.h" |
| #include "HTMLSlotElement.h" |
| #include "HTMLTableElement.h" |
| #include "HTMLTextAreaElement.h" |
| #include "HTMLVideoElement.h" |
| #include "LocalDOMWindow.h" |
| #include "LocalFrameView.h" |
| #include "MathMLElement.h" |
| #include "NodeName.h" |
| #include "Page.h" |
| #include "PathOperation.h" |
| #include "RenderBox.h" |
| #include "RenderStyle+GettersInlines.h" |
| #include "RenderStyle+SettersInlines.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "SVGElement.h" |
| #include "SVGGraphicsElement.h" |
| #include "SVGNames.h" |
| #include "SVGSVGElement.h" |
| #include "SVGURIReference.h" |
| #include "Settings.h" |
| #include "ShadowRoot.h" |
| #include "StylableInlines.h" |
| #include "StyleComputedStyle+InitialInlines.h" |
| #include "StylePrimitiveNumericTypes+Evaluation.h" |
| #include "StyleSelfAlignmentData.h" |
| #include "StyleTextDecorationLine.h" |
| #include "StyleUpdate.h" |
| #include "Styleable.h" |
| #include "Text.h" |
| #include "TouchAction.h" |
| #include "TypedElementDescendantIterator.h" |
| #include "TypedElementDescendantIteratorInlines.h" |
| #include "UserAgentParts.h" |
| #include "VisibilityAdjustment.h" |
| #include "WebAnimationTypes.h" |
| #include <wtf/RobinHoodHashSet.h> |
| |
| #if PLATFORM(COCOA) |
| #include <wtf/cocoa/RuntimeApplicationChecksCocoa.h> |
| #endif |
| |
| #if ENABLE(FULLSCREEN_API) |
| #include "DocumentFullscreen.h" |
| #endif |
| |
| namespace WebCore { |
| namespace Style { |
| |
| using namespace CSS::Literals; |
| using namespace HTMLNames; |
| |
| Adjuster::Adjuster(const Document& document, const RenderStyle& parentStyle, const RenderStyle* parentBoxStyle, Element* element) |
| : m_document(document) |
| , m_parentStyle(parentStyle) |
| , m_parentBoxStyle(parentBoxStyle ? *parentBoxStyle : m_parentStyle) |
| , m_element(element) |
| { |
| } |
| |
| #if PLATFORM(COCOA) |
| static void addIntrinsicMargins(RenderStyle& style) |
| { |
| // Intrinsic margin value. |
| const auto intrinsicMargin = Style::MarginEdge::Fixed { static_cast<float>(clampToInteger(2 * style.usedZoom())) }; |
| |
| // 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().isIntrinsicOrLegacyIntrinsicOrAuto()) { |
| if (style.marginLeft().hasQuirk()) |
| style.setMarginLeft(intrinsicMargin); |
| if (style.marginRight().hasQuirk()) |
| style.setMarginRight(intrinsicMargin); |
| } |
| |
| if (style.height().isAuto()) { |
| if (style.marginTop().hasQuirk()) |
| style.setMarginTop(intrinsicMargin); |
| if (style.marginBottom().hasQuirk()) |
| style.setMarginBottom(intrinsicMargin); |
| } |
| } |
| #endif |
| |
| // https://www.w3.org/TR/css-display-3/#transformations |
| static DisplayType equivalentBlockDisplay(const RenderStyle& style) |
| { |
| switch (auto display = style.display()) { |
| case DisplayType::Block: |
| case DisplayType::Table: |
| case DisplayType::Box: |
| case DisplayType::Flex: |
| case DisplayType::Grid: |
| case DisplayType::GridLanes: |
| case DisplayType::FlowRoot: |
| case DisplayType::ListItem: |
| case DisplayType::RubyBlock: |
| 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::InlineGridLanes: |
| return DisplayType::GridLanes; |
| case DisplayType::Ruby: |
| return DisplayType::RubyBlock; |
| |
| 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: |
| case DisplayType::RubyBase: |
| case DisplayType::RubyAnnotation: |
| 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; |
| } |
| |
| // https://www.w3.org/TR/css-display-3/#transformations |
| static DisplayType equivalentInlineDisplay(const RenderStyle& style) |
| { |
| switch (auto display = style.display()) { |
| case DisplayType::Block: |
| return DisplayType::InlineBlock; |
| case DisplayType::Table: |
| return DisplayType::InlineTable; |
| case DisplayType::Box: |
| return DisplayType::InlineBox; |
| case DisplayType::Flex: |
| return DisplayType::InlineFlex; |
| case DisplayType::Grid: |
| return DisplayType::InlineGrid; |
| case DisplayType::GridLanes: |
| return DisplayType::InlineGridLanes; |
| case DisplayType::RubyBlock: |
| return DisplayType::Ruby; |
| |
| case DisplayType::Inline: |
| case DisplayType::InlineBlock: |
| case DisplayType::InlineTable: |
| case DisplayType::InlineBox: |
| case DisplayType::InlineFlex: |
| case DisplayType::InlineGrid: |
| case DisplayType::InlineGridLanes: |
| case DisplayType::Ruby: |
| case DisplayType::RubyBase: |
| case DisplayType::RubyAnnotation: |
| return display; |
| |
| case DisplayType::FlowRoot: |
| case DisplayType::ListItem: |
| 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::Inline; |
| |
| case DisplayType::Contents: |
| ASSERT_NOT_REACHED(); |
| return DisplayType::Contents; |
| case DisplayType::None: |
| ASSERT_NOT_REACHED(); |
| return DisplayType::None; |
| } |
| ASSERT_NOT_REACHED(); |
| return DisplayType::Inline; |
| } |
| |
| static bool shouldInheritTextDecorationsInEffect(const RenderStyle& style, const Element* element) |
| { |
| if (style.isFloating() || style.hasOutOfFlowPosition()) |
| return false; |
| |
| // Media elements have a special rendering where the media controls do not use a proper containing |
| // block model which means we need to manually stop text-decorations to apply to text inside media controls. |
| auto isAtMediaUAShadowBoundary = [&] { |
| #if ENABLE(VIDEO) |
| if (!element) |
| return false; |
| RefPtr parentNode = element->parentNode(); |
| return parentNode && parentNode->isUserAgentShadowRoot() && parentNode->parentOrShadowHostElement()->isMediaElement(); |
| #else |
| return false; |
| #endif |
| }(); |
| |
| // Outermost <svg> roots are considered to be atomic inline-level. |
| if (RefPtr svgElement = dynamicDowncast<SVGElement>(element); svgElement && svgElement->isOutermostSVGSVGElement()) |
| return false; |
| |
| // There is no other good way to prevent decorations from affecting user agent shadow trees. |
| if (isAtMediaUAShadowBoundary) |
| return false; |
| |
| switch (style.display()) { |
| case DisplayType::InlineTable: |
| case DisplayType::InlineBlock: |
| case DisplayType::InlineGrid: |
| case DisplayType::InlineGridLanes: |
| case DisplayType::InlineFlex: |
| case DisplayType::InlineBox: |
| return false; |
| default: |
| break; |
| }; |
| |
| return true; |
| } |
| |
| static bool isScrollableOverflow(Overflow overflow) |
| { |
| return overflow == Overflow::Scroll || overflow == Overflow::Auto; |
| } |
| |
| static Style::TouchAction computeUsedTouchAction(const RenderStyle& style, Style::TouchAction usedTouchAction) |
| { |
| // 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) |
| usedTouchAction = Style::ComputedStyle::initialTouchAction(); |
| |
| auto touchAction = style.touchAction(); |
| if (touchAction == Style::ComputedStyle::initialTouchAction()) |
| return usedTouchAction; |
| |
| if (usedTouchAction.isNone() || touchAction.isNone()) |
| return CSS::Keyword::None { }; |
| |
| auto usedTouchActionEnumSet = usedTouchAction.tryEnumSet(); |
| if (!usedTouchActionEnumSet) |
| return touchAction; |
| |
| auto touchActionEnumSet = touchAction.tryEnumSet(); |
| if (!touchActionEnumSet) |
| return usedTouchAction; |
| |
| auto sharedTouchActions = *usedTouchActionEnumSet & *touchActionEnumSet; |
| if (sharedTouchActions.isEmpty()) |
| return CSS::Keyword::None { }; |
| |
| return sharedTouchActions; |
| } |
| |
| bool Adjuster::adjustEventListenerRegionTypesForRootStyle(RenderStyle& rootStyle, const Document& document) |
| { |
| auto regionTypes = computeEventListenerRegionTypes(document, rootStyle, document, { }); |
| if (RefPtr window = document.window()) |
| regionTypes.add(computeEventListenerRegionTypes(document, rootStyle, *window, { })); |
| |
| #if ENABLE(TOUCH_EVENT_REGIONS) |
| // https://html.spec.whatwg.org/multipage/popover.html#popover-light-dismiss |
| if (document.needsPointerEventHandlingForPopover()) { |
| regionTypes.add(EventListenerRegionType::PointerDown); |
| regionTypes.add(EventListenerRegionType::PointerUp); |
| } |
| #endif |
| |
| bool changed = regionTypes != rootStyle.eventListenerRegionTypes(); |
| rootStyle.setEventListenerRegionTypes(regionTypes); |
| return changed; |
| } |
| |
| OptionSet<EventListenerRegionType> Adjuster::computeEventListenerRegionTypes(const Document& document, const RenderStyle& style, const EventTarget& eventTarget, OptionSet<EventListenerRegionType> parentTypes) |
| { |
| auto types = parentTypes; |
| |
| #if ENABLE(WHEEL_EVENT_REGIONS) || ENABLE(TOUCH_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); |
| }; |
| #endif |
| #if ENABLE(WHEEL_EVENT_REGIONS) |
| if (eventTarget.hasEventListeners()) { |
| findListeners(eventNames().wheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel); |
| findListeners(eventNames().mousewheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel); |
| } |
| #endif |
| #if ENABLE(TOUCH_EVENT_REGIONS) |
| if (eventTarget.hasEventListeners()) { |
| findListeners(eventNames().touchstartEvent, EventListenerRegionType::TouchStart, EventListenerRegionType::NonPassiveTouchStart); |
| findListeners(eventNames().touchendEvent, EventListenerRegionType::TouchEnd, EventListenerRegionType::NonPassiveTouchEnd); |
| // `touchcancel` is sent after the event has already been cancelled. Calling preventDefault() has no effect, so we don't |
| // need a synchronous version. |
| findListeners(eventNames().touchcancelEvent, EventListenerRegionType::TouchCancel, EventListenerRegionType::TouchCancel); |
| findListeners(eventNames().touchmoveEvent, EventListenerRegionType::TouchMove, EventListenerRegionType::NonPassiveTouchMove); |
| findListeners(eventNames().touchforcechangeEvent, EventListenerRegionType::TouchForceChange, EventListenerRegionType::NonPassiveTouchForceChange); |
| |
| findListeners(eventNames().pointerdownEvent, EventListenerRegionType::PointerDown, EventListenerRegionType::NonPassivePointerDown); |
| findListeners(eventNames().pointerenterEvent, EventListenerRegionType::PointerEnter, EventListenerRegionType::NonPassivePointerEnter); |
| findListeners(eventNames().pointerleaveEvent, EventListenerRegionType::PointerLeave, EventListenerRegionType::NonPassivePointerLeave); |
| findListeners(eventNames().pointermoveEvent, EventListenerRegionType::PointerMove, EventListenerRegionType::NonPassivePointerMove); |
| findListeners(eventNames().pointeroutEvent, EventListenerRegionType::PointerOut, EventListenerRegionType::NonPassivePointerOut); |
| findListeners(eventNames().pointeroverEvent, EventListenerRegionType::PointerOver, EventListenerRegionType::NonPassivePointerOver); |
| findListeners(eventNames().pointerupEvent, EventListenerRegionType::PointerUp, EventListenerRegionType::NonPassivePointerUp); |
| if (document.quirks().shouldDispatchSimulatedMouseEvents(&eventTarget)) { |
| findListeners(eventNames().mousedownEvent, EventListenerRegionType::MouseDown, EventListenerRegionType::NonPassiveMouseDown); |
| findListeners(eventNames().mouseupEvent, EventListenerRegionType::MouseUp, EventListenerRegionType::NonPassiveMouseUp); |
| findListeners(eventNames().mousemoveEvent, EventListenerRegionType::MouseMove, EventListenerRegionType::NonPassiveMouseMove); |
| } |
| |
| findListeners(eventNames().gesturechangeEvent, EventListenerRegionType::GestureChange, EventListenerRegionType::NonPassiveGestureChange); |
| findListeners(eventNames().gestureendEvent, EventListenerRegionType::GestureEnd, EventListenerRegionType::NonPassiveGestureEnd); |
| findListeners(eventNames().gesturestartEvent, EventListenerRegionType::GestureStart, EventListenerRegionType::NonPassiveGestureStart); |
| } |
| |
| if (eventTarget.hasInternalTouchEventHandling()) { |
| types.add(EventListenerRegionType::TouchCancel); |
| types.add(EventListenerRegionType::TouchEnd); |
| types.add(EventListenerRegionType::TouchForceChange); |
| types.add(EventListenerRegionType::TouchMove); |
| types.add(EventListenerRegionType::TouchStart); |
| types.add(EventListenerRegionType::NonPassiveTouchEnd); |
| types.add(EventListenerRegionType::NonPassiveTouchForceChange); |
| types.add(EventListenerRegionType::NonPassiveTouchMove); |
| types.add(EventListenerRegionType::NonPassiveTouchStart); |
| } |
| #endif |
| |
| #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION) |
| if (document.page() && document.page()->shouldBuildInteractionRegions()) { |
| if (const auto* node = dynamicDowncast<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; |
| } |
| |
| static bool isOverflowClipOrVisible(Overflow overflow) |
| { |
| return overflow == Overflow::Clip || overflow == Overflow::Visible; |
| } |
| |
| static bool shouldInlinifyForRuby(const RenderStyle& style, const RenderStyle& parentBoxStyle) |
| { |
| auto parentDisplay = parentBoxStyle.display(); |
| auto hasRubyParent = parentDisplay == DisplayType::Ruby |
| || parentDisplay == DisplayType::RubyBlock |
| || parentDisplay == DisplayType::RubyAnnotation |
| || parentDisplay == DisplayType::RubyBase; |
| |
| return hasRubyParent && !style.hasOutOfFlowPosition() && !style.isFloating(); |
| } |
| |
| static bool hasUnsupportedRubyDisplay(DisplayType display, const Element* element) |
| { |
| // Only allow ruby elements to have ruby display types for now. |
| switch (display) { |
| case DisplayType::Ruby: |
| case DisplayType::RubyBlock: |
| // Test for localName so this also allows WebVTT ruby elements. |
| return !element || !element->hasLocalName(rubyTag->localName()); |
| case DisplayType::RubyAnnotation: |
| return !element || !element->hasLocalName(rtTag->localName()); |
| case DisplayType::RubyBase: |
| ASSERT_NOT_REACHED(); |
| return false; |
| default: |
| return false; |
| } |
| } |
| |
| // https://drafts.csswg.org/css-ruby-1/#bidi |
| static UnicodeBidi forceBidiIsolationForRuby(UnicodeBidi unicodeBidi) |
| { |
| switch (unicodeBidi) { |
| case UnicodeBidi::Normal: |
| case UnicodeBidi::Embed: |
| case UnicodeBidi::Isolate: |
| return UnicodeBidi::Isolate; |
| case UnicodeBidi::Override: |
| case UnicodeBidi::IsolateOverride: |
| return UnicodeBidi::IsolateOverride; |
| case UnicodeBidi::Plaintext: |
| return UnicodeBidi::Plaintext; |
| } |
| ASSERT_NOT_REACHED(); |
| return UnicodeBidi::Isolate; |
| } |
| |
| static bool shouldTreatAutoZIndexAsZero(const RenderStyle& style) |
| { |
| return style.hasOpacity() |
| || style.hasTransformRelatedProperty() |
| || style.hasMask() |
| || style.hasClipPath() |
| || style.hasBoxReflect() |
| || style.hasFilter() |
| || style.hasBackdropFilter() |
| #if HAVE(CORE_MATERIAL) |
| || style.hasAppleVisualEffect() |
| #endif |
| || style.hasBlendMode() |
| || style.hasIsolation() |
| || style.position() == PositionType::Sticky |
| || style.position() == PositionType::Fixed |
| || style.willChange().canCreateStackingContext(); |
| } |
| |
| void Adjuster::adjustFromBuilder(RenderStyle& style) |
| { |
| // Do some adjustments that don't depend on element or parent style and are safe to cache. |
| // This allows copy-on-write to trigger before caching. |
| |
| if (style.specifiedZIndex().isAuto()) { |
| if (shouldTreatAutoZIndexAsZero(style)) |
| style.setUsedZIndex(0); |
| } else if (style.position() != PositionType::Static) |
| style.setUsedZIndex(style.specifiedZIndex()); |
| |
| // Adjust any coordinated value lists. |
| style.adjustAnimations(); |
| style.adjustTransitions(); |
| style.adjustBackgroundLayers(); |
| style.adjustMaskLayers(); |
| |
| // Do the same for scroll-timeline and view-timeline longhands. |
| style.adjustScrollTimelines(); |
| style.adjustViewTimelines(); |
| } |
| |
| void Adjuster::adjustFirstLetterStyle(RenderStyle& style) |
| { |
| if (style.pseudoElementType() != PseudoElementType::FirstLetter) |
| return; |
| |
| // Force inline display (except for floating first-letters). |
| style.setEffectiveDisplay(style.isFloating() ? DisplayType::Block : DisplayType::Inline); |
| } |
| |
| void Adjuster::adjustFirstLineStyle(RenderStyle& style) |
| { |
| if (style.pseudoElementType() != PseudoElementType::FirstLine) |
| return; |
| |
| // Force inline display. |
| style.setEffectiveDisplay(DisplayType::Inline); |
| } |
| |
| void Adjuster::adjust(RenderStyle& style) const |
| { |
| if (style.display() == DisplayType::Contents) |
| adjustDisplayContentsStyle(style); |
| |
| if (m_element && (m_element->hasTagName(frameTag) || m_element->hasTagName(framesetTag))) { |
| // Framesets ignore display, position and float properties. |
| style.setPosition(PositionType::Static); |
| style.setEffectiveDisplay(DisplayType::Block); |
| style.setFloating(Float::None); |
| } |
| |
| if (style.display() != DisplayType::None && style.display() != DisplayType::Contents) { |
| if (RefPtr element = m_element) { |
| // Tables never support the -webkit-* values for text-align and will reset back to the default. |
| if (is<HTMLTableElement>(*element) && (style.textAlign() == TextAlign::WebKitLeft || style.textAlign() == TextAlign::WebKitCenter || style.textAlign() == TextAlign::WebKitRight)) |
| style.setTextAlign(TextAlign::Start); |
| |
| // Ruby text does not support float or position. This might change with evolution of the specification. |
| if (element->hasTagName(rtTag)) { |
| style.setPosition(PositionType::Static); |
| style.setFloating(Float::None); |
| } |
| |
| if (element->hasTagName(legendTag)) |
| style.setEffectiveDisplay(equivalentBlockDisplay(style)); |
| } |
| |
| if (hasUnsupportedRubyDisplay(style.display(), m_element.get())) |
| style.setEffectiveDisplay(style.display() == DisplayType::RubyBlock ? DisplayType::Block : DisplayType::Inline); |
| |
| // Top layer elements are always position: absolute; unless the position is set to fixed. |
| // https://fullscreen.spec.whatwg.org/#new-stacking-layer |
| if (m_element != m_document->documentElement() && style.position() != PositionType::Absolute && style.position() != PositionType::Fixed && isInTopLayerOrBackdrop(style, m_element.get())) |
| 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.get())) |
| style.setEffectiveDisplay(equivalentBlockDisplay(style)); |
| |
| adjustFirstLetterStyle(style); |
| adjustFirstLineStyle(style); |
| |
| // 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.pseudoElementType() && style.writingMode().computedWritingMode() != m_parentStyle.writingMode().computedWritingMode()) |
| 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. |
| 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.setWritingMode(m_parentStyle.writingMode().computedWritingMode()); |
| |
| |
| // FIXME: Adjust this once CSSWG clarifies exactly how the initial value should compute on other display types. |
| // For now, this gives mostly backwards-compatible behavior. |
| if (style.display() == DisplayType::Grid || style.display() == DisplayType::InlineGrid) { |
| if (style.gridAutoFlow().direction() == GridAutoFlow::Direction::Normal) |
| style.setGridAutoFlowDirection(Style::GridAutoFlow::Direction::Row); |
| } else if (style.display() == DisplayType::GridLanes || style.display() == DisplayType::InlineGridLanes) { |
| if (style.gridAutoFlow().direction() == GridAutoFlow::Direction::Normal) { |
| if (!style.gridTemplateRows().isNone() && style.gridTemplateColumns().isNone()) |
| style.setGridAutoFlowDirection(Style::GridAutoFlow::Direction::Column); |
| else |
| style.setGridAutoFlowDirection(Style::GridAutoFlow::Direction::Row); |
| } |
| } |
| |
| if (style.isDisplayDeprecatedFlexibleBox()) { |
| // FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting |
| // of block-flow to anything other than StyleWritingMode::HorizontalTb. |
| // https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support. |
| style.setWritingMode(StyleWritingMode::HorizontalTb); |
| } |
| |
| if (m_parentBoxStyle.isDisplayDeprecatedFlexibleBox()) |
| style.setFloating(Float::None); |
| |
| // 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.isDisplayFlexibleOrGridFormattingContextBox()) { |
| style.setFloating(Float::None); |
| style.setEffectiveDisplay(equivalentBlockDisplay(style)); |
| } |
| |
| // https://www.w3.org/TR/css-ruby-1/#anon-gen-inlinize |
| if (shouldInlinifyForRuby(style, m_parentBoxStyle)) |
| style.setEffectiveDisplay(equivalentInlineDisplay(style)); |
| // https://drafts.csswg.org/css-ruby-1/#bidi |
| if (style.isRubyContainerOrInternalRubyBox()) |
| style.setUnicodeBidi(forceBidiIsolationForRuby(style.unicodeBidi())); |
| } |
| |
| auto hasAutoZIndex = [](const RenderStyle& style, const RenderStyle& parentBoxStyle, const Element* element) { |
| if (style.specifiedZIndex().isAuto()) |
| return true; |
| |
| // SVG2: Contrary to the rules in CSS 2.1, the z-index property applies to all SVG elements regardless |
| // of the value of the position property, with one exception: as for boxes in CSS 2.1, outer ‘svg’ elements |
| // must be positioned for z-index to apply to them. |
| if (element && element->document().settings().layerBasedSVGEngineEnabled()) { |
| if (RefPtr svgElement = dynamicDowncast<SVGElement>(*element); svgElement && svgElement->isOutermostSVGSVGElement()) |
| return element->renderer() && element->renderer()->style().position() == PositionType::Static; |
| |
| return false; |
| } |
| |
| // Make sure our z-index value is only applied if the object is positioned. |
| return style.position() == PositionType::Static && !parentBoxStyle.isDisplayFlexibleOrGridFormattingContextBox(); |
| }; |
| |
| bool hasAutoSpecifiedZIndex = hasAutoZIndex(style, m_parentBoxStyle, m_element.get()); |
| |
| // 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. |
| // Same for the additional translation component present in RenderSVGTransformableContainer (that stems from <use> x/y |
| // properties, that are transferred to the internal RenderSVGTransformableContainer), or for the viewBox-induced transformation |
| // in RenderSVGViewportContainer. They all need to return true for 'hasTransformRelatedProperty'. |
| auto hasTransformRelatedProperty = [](const RenderStyle& style, const Element* element, const RenderStyle& parentStyle) { |
| if (element && element->document().settings().css3DTransformBackfaceVisibilityInteroperabilityEnabled() && style.backfaceVisibility() == BackfaceVisibility::Hidden && parentStyle.preserves3D()) |
| return true; |
| |
| if (style.hasTransformRelatedProperty()) |
| return true; |
| |
| if (element && element->document().settings().layerBasedSVGEngineEnabled()) { |
| if (auto* graphicsElement = dynamicDowncast<SVGGraphicsElement>(element); graphicsElement && graphicsElement->hasTransformRelatedAttributes()) |
| return true; |
| } |
| |
| 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 (hasAutoSpecifiedZIndex) { |
| if ((m_element && m_document->documentElement() == m_element.get()) |
| || hasTransformRelatedProperty(style, m_element.get(), m_parentStyle) |
| || shouldTreatAutoZIndexAsZero(style) |
| || isInTopLayerOrBackdrop(style, m_element.get())) |
| style.setUsedZIndex(0); |
| else |
| style.setUsedZIndex(CSS::Keyword::Auto { }); |
| } else |
| style.setUsedZIndex(style.specifiedZIndex()); |
| |
| if (RefPtr element = m_element) { |
| // Textarea considers overflow visible as auto. |
| if (is<HTMLTextAreaElement>(*element)) { |
| style.setOverflowX(style.overflowX() == Overflow::Visible ? Overflow::Auto : style.overflowX()); |
| style.setOverflowY(style.overflowY() == Overflow::Visible ? Overflow::Auto : style.overflowY()); |
| } |
| |
| if (RefPtr input = dynamicDowncast<HTMLInputElement>(*element); input && input->isPasswordField()) |
| style.setTextSecurity(style.inputSecurity() == InputSecurity::Auto ? TextSecurity::Disc : TextSecurity::None); |
| |
| // Disallow -webkit-user-modify on ::pseudo elements, except if that pseudo-element targets a slot, |
| // in which case we want the editability to be passed onto the slotted contents. |
| if (element->isInUserAgentShadowTree() && !element->userAgentPart().isNull() && !is<HTMLSlotElement>(element)) |
| style.setUserModify(UserModify::ReadOnly); |
| |
| if (is<HTMLMarqueeElement>(*element)) { |
| bool isVertical = style.marqueeDirection() == MarqueeDirection::Up || style.marqueeDirection() == MarqueeDirection::Down; |
| // Make horizontal marquees not wrap. |
| if (!isVertical) { |
| style.setWhiteSpaceCollapse(WhiteSpaceCollapse::Collapse); |
| style.setTextWrapMode(TextWrapMode::NoWrap); |
| style.setTextAlign(TextAlign::Start); |
| } |
| // Apparently this is the expected legacy behavior. |
| if (isVertical && style.height().isAuto()) |
| style.setHeight(200_css_px); |
| } |
| |
| if (m_element->visibilityAdjustment().contains(VisibilityAdjustment::Subtree)) [[unlikely]] |
| style.setIsForceHidden(); |
| |
| if (m_element->invokedPopover()) |
| style.setIsPopoverInvoker(); |
| |
| if (m_document->settings().detailsAutoExpandEnabled() && m_element->isInUserAgentShadowTree() && m_element->userAgentPart() == UserAgentParts::detailsContent()) |
| style.setAutoRevealsWhenFound(); |
| |
| if (RefPtr htmlElement = dynamicDowncast<HTMLElement>(element); htmlElement && htmlElement->isHiddenUntilFound()) |
| style.setAutoRevealsWhenFound(); |
| } |
| |
| if (shouldInheritTextDecorationsInEffect(style, m_element.get())) |
| style.addToTextDecorationLineInEffect(style.textDecorationLine()); |
| else |
| style.setTextDecorationLineInEffect(Style::TextDecorationLine { style.textDecorationLine() }); |
| |
| bool overflowIsClipOrVisible = isOverflowClipOrVisible(style.overflowY()) && isOverflowClipOrVisible(style.overflowX()); |
| |
| if (!overflowIsClipOrVisible && (style.display() == DisplayType::Table || style.display() == DisplayType::InlineTable)) { |
| // Tables only support overflow:hidden and overflow:visible and ignore anything else, |
| // see https://drafts.csswg.org/css2/#overflow. As a table is not a block |
| // container box the rules for resolving conflicting x and y values in CSS Overflow Module |
| // Level 3 do not apply. Arguably overflow-x and overflow-y aren't allowed on tables but |
| // all UAs allow it. |
| if (style.overflowX() != Overflow::Hidden) |
| style.setOverflowX(Overflow::Visible); |
| if (style.overflowY() != Overflow::Hidden) |
| style.setOverflowY(Overflow::Visible); |
| // If we are left with conflicting overflow values for the x and y axes on a table then resolve |
| // both to Overflow::Visible. This is interoperable behaviour but is not specced anywhere. |
| if (style.overflowX() == Overflow::Visible) |
| style.setOverflowY(Overflow::Visible); |
| else if (style.overflowY() == Overflow::Visible) |
| style.setOverflowX(Overflow::Visible); |
| } else if (!isOverflowClipOrVisible(style.overflowY())) { |
| // 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. |
| // Values of 'clip' and 'visible' can only be used with 'clip' and 'visible'. |
| // If they aren't, 'clip' and 'visible' is reset. |
| if (style.overflowX() == Overflow::Visible) |
| style.setOverflowX(Overflow::Auto); |
| else if (style.overflowX() == Overflow::Clip) |
| style.setOverflowX(Overflow::Hidden); |
| } else if (!isOverflowClipOrVisible(style.overflowX())) { |
| // Values of 'clip' and 'visible' can only be used with 'clip' and 'visible'. |
| // If they aren't, 'clip' and 'visible' is reset. |
| if (style.overflowY() == Overflow::Visible) |
| style.setOverflowY(Overflow::Auto); |
| else if (style.overflowY() == Overflow::Clip) |
| style.setOverflowY(Overflow::Hidden); |
| } |
| |
| // 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)); |
| |
| #if ENABLE(WEBKIT_OVERFLOW_SCROLLING_CSS_PROPERTY) |
| // Touch overflow scrolling creates a stacking context. |
| if (style.usedZIndex().isAuto() && style.overflowScrolling() == Style::WebkitOverflowScrolling::Touch && (isScrollableOverflow(style.overflowX()) || isScrollableOverflow(style.overflowY()))) |
| style.setUsedZIndex(0); |
| #endif |
| |
| #if PLATFORM(COCOA) |
| static const bool shouldAddIntrinsicMarginToFormControls = !linkedOnOrAfterSDKWithBehavior(SDKAlignedBehavior::DoesNotAddIntrinsicMarginsToFormControls); |
| if (shouldAddIntrinsicMarginToFormControls) { |
| // 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.computedFontSize() >= 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 (RefPtr input = dynamicDowncast<HTMLInputElement>(*m_element); !input || !input->isImageButton()) |
| addIntrinsicMargins(style); |
| } |
| } |
| #endif |
| |
| // Let the theme also have a crack at adjusting the style. |
| if (style.hasAppearance()) |
| adjustThemeStyle(style, m_parentStyle); |
| |
| // This should be kept in sync with requiresRenderingConsolidationForViewTransition |
| if (style.preserves3D()) { |
| bool forceToFlat = style.overflowX() != Overflow::Visible |
| || style.hasOpacity() |
| || style.overflowY() != Overflow::Visible |
| || style.hasClip() |
| || style.hasClipPath() |
| || style.hasFilter() |
| || style.hasIsolation() |
| || style.hasMask() |
| || style.hasBackdropFilter() |
| #if HAVE(CORE_MATERIAL) |
| || style.hasAppleVisualEffect() |
| #endif |
| || style.hasBlendMode() |
| || !style.viewTransitionName().isNone(); |
| if (RefPtr element = m_element) { |
| auto styleable = Styleable::fromElement(*element); |
| forceToFlat |= styleable.capturedInViewTransition(); |
| } |
| style.setTransformStyleForcedToFlat(forceToFlat); |
| } |
| |
| style.setIsEffectivelyTransparent(style.opacity().isTransparent() || m_parentStyle.isEffectivelyTransparent()); |
| |
| if (RefPtr element = dynamicDowncast<SVGElement>(m_element)) |
| adjustSVGElementStyle(style, *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().isLegacy() && style.justifyItems().isLegacyNone()) |
| style.setJustifyItems(m_parentBoxStyle.justifyItems()); |
| |
| #if HAVE(CORE_MATERIAL) |
| if (appleVisualEffectNeedsBackdrop(style.appleVisualEffect())) |
| style.setUsedAppleVisualEffectForSubtree(style.appleVisualEffect()); |
| else |
| style.setUsedAppleVisualEffectForSubtree(m_parentStyle.usedAppleVisualEffectForSubtree()); |
| #endif |
| |
| style.setUsedTouchAction(computeUsedTouchAction(style, m_parentStyle.usedTouchAction())); |
| |
| // Counterpart in Element::addToTopLayer/removeFromTopLayer! |
| auto hasInertAttribute = [] (const Element* element) -> bool { |
| return 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; |
| #if ENABLE(FULLSCREEN_API) |
| if (RefPtr documentFullscreen = m_document->fullscreenIfExists(); documentFullscreen && documentFullscreen->fullscreenElement() && element == m_document->documentElement()) |
| return true; |
| #endif |
| return false; |
| }; |
| if (isInertSubtreeRoot(m_element.get())) |
| style.setEffectiveInert(true); |
| |
| if (RefPtr element = m_element) { |
| // Make sure the active dialog is interactable when the whole document is blocked by the modal dialog |
| if (element == m_document->activeModalDialog() && !hasInertAttribute(element.get())) |
| style.setEffectiveInert(false); |
| |
| #if ENABLE(FULLSCREEN_API) |
| if (RefPtr documentFullscreen = m_document->fullscreenIfExists(); documentFullscreen && m_element == documentFullscreen->fullscreenElement() && !hasInertAttribute(m_element.get())) |
| style.setEffectiveInert(false); |
| #endif |
| |
| style.setEventListenerRegionTypes(computeEventListenerRegionTypes(m_document, style, *m_element, m_parentStyle.eventListenerRegionTypes())); |
| |
| #if ENABLE(INTERACTION_REGIONS_IN_EVENT_REGION) |
| // Every element will automatically get an interaction region which is not useful, ignoring the `cursor: pointer;` on the body. |
| if (is<HTMLBodyElement>(*m_element) && style.cursorType() == CursorType::Pointer && style.eventListenerRegionTypes().contains(EventListenerRegionType::MouseClick)) |
| style.setCursor(CSS::Keyword::Auto { }); |
| #endif |
| |
| #if ENABLE(TEXT_AUTOSIZING) |
| if (m_document->settings().textAutosizingUsesIdempotentMode()) |
| adjustForTextAutosizing(style, *m_element); |
| #endif |
| } |
| |
| if (m_parentStyle.contentVisibility() != ContentVisibility::Hidden) { |
| if (m_element && isSkippedContentRoot(style, *m_element)) |
| style.setUsedContentVisibility(style.contentVisibility()); |
| } |
| if (style.contentVisibility() == ContentVisibility::Auto) { |
| style.containIntrinsicWidthAddAuto(); |
| style.containIntrinsicHeightAddAuto(); |
| } |
| |
| adjustForSiteSpecificQuirks(style); |
| } |
| |
| static bool hasEffectiveDisplayNoneForDisplayContents(const Element& element) |
| { |
| using namespace ElementNames; |
| |
| // 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; |
| |
| // https://drafts.csswg.org/css-display-3/#unbox-html |
| switch (element.elementName()) { |
| case HTML::br: |
| case HTML::wbr: |
| case HTML::meter: |
| case HTML::applet: |
| case HTML::progress: |
| case HTML::canvas: |
| case HTML::embed: |
| case HTML::object: |
| case HTML::audio: |
| case HTML::iframe: |
| case HTML::img: |
| case HTML::video: |
| case HTML::frame: |
| case HTML::frameset: |
| case HTML::input: |
| case HTML::textarea: |
| case HTML::select: |
| return true; |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| void Adjuster::adjustDisplayContentsStyle(RenderStyle& style) const |
| { |
| bool isInTopLayer = isInTopLayerOrBackdrop(style, m_element.get()); |
| if (isInTopLayer || m_document->documentElement() == m_element.get()) { |
| style.setEffectiveDisplay(DisplayType::Block); |
| return; |
| } |
| |
| if (!m_element && style.pseudoElementType() != PseudoElementType::Before && style.pseudoElementType() != PseudoElementType::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 |
| if (!svgElement.isOutermostSVGSVGElement()) |
| style.setPosition(Style::ComputedStyle::initialPosition()); |
| |
| // 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::adjust() - for those cases assertions were added. |
| if (svgElement.document().settings().layerBasedSVGEngineEnabled() && style.usedZIndex().isAuto()) { |
| // adjust() has already assigned a z-index of 0 if clip / filter is present or the element is the root element. |
| ASSERT(!style.hasClipPath()); |
| ASSERT(!style.hasFilter()); |
| |
| if (svgElement.isOutermostSVGSVGElement() |
| || 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) |
| || (svgElement.isInnerSVGSVGElement() && (style.overflowX() != Overflow::Visible || style.overflowY() != Overflow::Visible)) |
| || style.hasPositionedMask()) |
| style.setUsedZIndex(0); |
| } |
| |
| // (Legacy)RenderSVGRoot handles zooming for the whole SVG subtree, so foreignObject content should |
| // not be scaled again. |
| if (svgElement.hasTagName(SVGNames::foreignObjectTag)) |
| style.setUsedZoom(Style::evaluate<float>(Style::ComputedStyle::initialZoom())); |
| |
| // SVG text layout code expects us to be a block-level style element. |
| // While in theory any block level element would work (flex, grid etc), since we construct RenderBlockFlow for both foreign object and svg text, |
| // in practice only block layout happens here. |
| if ((svgElement.hasTagName(SVGNames::foreignObjectTag) || svgElement.hasTagName(SVGNames::textTag)) && generatesBox(style)) |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| |
| void Adjuster::adjustAnimatedStyle(RenderStyle& style, OptionSet<AnimationImpact> impact) const |
| { |
| adjust(style); |
| |
| // 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.usedZIndex().isAuto() && impact.contains(AnimationImpact::ForcesStackingContext)) |
| style.setUsedZIndex(0); |
| } |
| |
| void Adjuster::adjustThemeStyle(RenderStyle& style, const RenderStyle& parentStyle) const |
| { |
| ASSERT(style.hasAppearance()); |
| auto isOldWidthAuto = style.width().isAuto(); |
| auto isOldMinWidthAuto = style.minWidth().isAuto(); |
| auto isOldHeightAuto = style.height().isAuto(); |
| auto isOldMinHeightAuto = style.minHeight().isAuto(); |
| |
| RenderTheme::singleton().adjustStyle(style, parentStyle, m_element.get()); |
| |
| if (style.usedContain().contains(Style::ContainValue::Size)) { |
| if (!style.containIntrinsicWidth().isNone()) { |
| if (isOldWidthAuto) |
| style.setWidth(CSS::Keyword::Auto { }); |
| if (isOldMinWidthAuto) |
| style.setMinWidth(CSS::Keyword::Auto { }); |
| } |
| if (!style.containIntrinsicHeight().isNone()) { |
| if (isOldHeightAuto) |
| style.setHeight(CSS::Keyword::Auto { }); |
| if (isOldMinHeightAuto) |
| style.setMinHeight(CSS::Keyword::Auto { }); |
| } |
| } |
| } |
| |
| void Adjuster::adjustForSiteSpecificQuirks(RenderStyle& style) const |
| { |
| if (!m_element) |
| return; |
| |
| const auto& documentQuirks = m_document->quirks(); |
| |
| if (!documentQuirks.hasRelevantQuirks()) |
| return; |
| |
| if (documentQuirks.needsBodyScrollbarWidthNoneDisabledQuirk() && is<HTMLBodyElement>(*m_element)) { |
| if (style.scrollbarWidth() == ScrollbarWidth::None) |
| style.setScrollbarWidth(ScrollbarWidth::Auto); |
| } |
| |
| if (documentQuirks.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 (documentQuirks.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 (documentQuirks.needsGeforcenowWarningDisplayNoneQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> overlayClassName("cdk-overlay-container"_s); |
| static MainThreadNeverDestroyed<const AtomString> unsupportedClassName("unsupported-scenario-container"_s); |
| if (is<HTMLDivElement>(*m_element) && (m_element->hasClassName(overlayClassName) || m_element->hasClassName(unsupportedClassName))) |
| style.setEffectiveDisplay(DisplayType::None); |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| if (documentQuirks.needsGoogleMapsScrollingQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> className("PUtLdf"_s); |
| if (is<HTMLBodyElement>(*m_element) && m_element->hasClassName(className)) |
| style.setUsedTouchAction(CSS::Keyword::Auto { }); |
| } |
| if (documentQuirks.needsFacebookStoriesCreationFormQuirk(*m_element, style)) |
| style.setEffectiveDisplay(DisplayType::Flex); |
| #endif // PLATFORM(IOS_FAMILY) |
| |
| if (documentQuirks.needsFacebookRemoveNotSupportedQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> className("xnw9j1v"_s); |
| if (is<HTMLDivElement>(*m_element) && m_element->hasClassName(className)) |
| style.setEffectiveDisplay(DisplayType::None); |
| } |
| |
| if (documentQuirks.needsPrimeVideoUserSelectNoneQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> className("webPlayerSDKUiContainer"_s); |
| if (m_element->hasClassName(className)) |
| style.setUserSelect(UserSelect::None); |
| } |
| |
| if (auto tikTokOverflowingContentQuery = documentQuirks.needsTikTokOverflowingContentQuirk(*m_element, m_parentStyle)) { |
| if (*tikTokOverflowingContentQuery == Quirks::TikTokOverflowingContentQuirkType::CommentsSectionQuirk) { |
| style.setFlexShrink({ 1 }); |
| style.setMinWidth(0_css_px); |
| } else { |
| ASSERT(tikTokOverflowingContentQuery == Quirks::TikTokOverflowingContentQuirkType::VideoSectionQuirk); |
| style.setFlexShrink({ 2 }); |
| } |
| } |
| |
| #if ENABLE(VIDEO) |
| if (documentQuirks.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); |
| |
| if (m_element->hasClassName(instreamNativeVideoDivClass)) { |
| RefPtr video = dynamicDowncast<HTMLVideoElement>(m_element->treeScope().getElementById(videoElementID)); |
| if (video && video->isFullscreen()) |
| style.setEffectiveDisplay(DisplayType::Block); |
| } |
| } |
| } |
| #if ENABLE(FULLSCREEN_API) |
| if (RefPtr documentFullscreen = m_document->fullscreenIfExists(); documentFullscreen && documentQuirks.needsFullscreenObjectFitQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> playerClassName("top-player-video-element"_s); |
| bool isFullscreen = documentFullscreen->isFullscreen(); |
| if (is<HTMLVideoElement>(*m_element) && isFullscreen && m_element->hasClassName(playerClassName) && style.objectFit() == ObjectFit::Fill) |
| style.setObjectFit(ObjectFit::Contain); |
| } |
| #endif |
| #endif |
| |
| if (documentQuirks.needsHotelsAnimationQuirk(*m_element, style)) { |
| // We need to reset animation styles that are mistakenly overridden: |
| // animation-delay: 0s, 0.06s; |
| // animation-duration: 0.18s, 0.06s; |
| // animation-fill-mode: none, forwards; |
| // animation-name: menu-grow-left, menu-fade-in; |
| auto menuGrowLeftAnimation = Style::Animation { { ScopedName { "menu-grow-left"_s } } }; |
| menuGrowLeftAnimation.setDuration(.18_css_s); |
| |
| auto menuFadeInAnimation = Style::Animation { { ScopedName { "menu-fade-in"_s } } }; |
| menuFadeInAnimation.setDelay(.06_css_s); |
| menuFadeInAnimation.setDuration(.06_css_s); |
| menuFadeInAnimation.setFillMode(AnimationFillMode::Forwards); |
| |
| auto& animations = style.ensureAnimations(); |
| animations.append(WTF::move(menuGrowLeftAnimation)); |
| animations.append(WTF::move(menuFadeInAnimation)); |
| } |
| |
| #if PLATFORM(IOS_FAMILY) |
| if (documentQuirks.needsClaudeSidebarViewportUnitQuirk(*m_element, style)) |
| style.setHeight(Style::PreferredSize::Fixed { m_document->renderView()->sizeForCSSDynamicViewportUnits().height() }); |
| #endif |
| |
| #if PLATFORM(MAC) |
| if (documentQuirks.needsZomatoEmailLoginLabelQuirk()) { |
| static MainThreadNeverDestroyed<const AtomString> class1("eNjKGZ"_s); |
| if (is<HTMLLabelElement>(*m_element) |
| && m_element->hasClassName(class1) |
| && style.backgroundColor() == Color { WebCore::Color::white }) |
| style.setBackgroundColor({ WebCore::Color::transparentBlack }); |
| } |
| #endif |
| |
| if (documentQuirks.needsInstagramResizingReelsQuirk(*m_element, style, m_parentStyle)) |
| style.setFlexGrow(1); |
| } |
| |
| void Adjuster::propagateToDocumentElementAndInitialContainingBlock(Update& update, const Document& document) |
| { |
| RefPtr body = document.body(); |
| auto* bodyStyle = body ? update.elementStyle(*body) : nullptr; |
| auto* documentElementStyle = update.elementStyle(*document.documentElement()); |
| |
| if (!documentElementStyle) |
| return; |
| |
| // https://drafts.csswg.org/css-contain-2/#contain-property |
| // "Additionally, when any containments are active on either the HTML html or body elements, propagation of |
| // properties from the body element to the initial containing block, the viewport, or the canvas background, is disabled." |
| auto shouldPropagateFromBody = [&] { |
| if (bodyStyle && !bodyStyle->usedContain().isNone()) |
| return false; |
| return documentElementStyle->usedContain().isNone(); |
| }(); |
| |
| auto writingMode = [&] { |
| if (shouldPropagateFromBody && bodyStyle && bodyStyle->hasExplicitlySetWritingMode()) |
| return bodyStyle->writingMode().computedWritingMode(); |
| if (documentElementStyle->hasExplicitlySetWritingMode()) |
| return documentElementStyle->writingMode().computedWritingMode(); |
| return Style::ComputedStyle::initialWritingMode(); |
| }(); |
| |
| auto direction = [&] { |
| if (documentElementStyle->hasExplicitlySetDirection()) |
| return documentElementStyle->writingMode().computedTextDirection(); |
| if (shouldPropagateFromBody && bodyStyle && bodyStyle->hasExplicitlySetDirection()) |
| return bodyStyle->writingMode().computedTextDirection(); |
| return Style::ComputedStyle::initialDirection(); |
| }(); |
| |
| // https://drafts.csswg.org/css-writing-modes-3/#icb |
| WritingMode viewWritingMode = document.renderView()->writingMode(); |
| if (writingMode != viewWritingMode.computedWritingMode() || direction != viewWritingMode.computedTextDirection()) { |
| auto newRootStyle = RenderStyle::clonePtr(document.renderView()->style()); |
| newRootStyle->setWritingMode(writingMode); |
| newRootStyle->setDirection(direction); |
| newRootStyle->setColumnStylesFromPaginationMode(document.view()->pagination().mode); |
| update.addInitialContainingBlockUpdate(WTF::move(newRootStyle)); |
| } |
| |
| // https://drafts.csswg.org/css-writing-modes-3/#principal-flow |
| if (writingMode != documentElementStyle->writingMode().computedWritingMode() || direction != documentElementStyle->writingMode().computedTextDirection()) { |
| auto* documentElementUpdate = update.elementUpdate(*document.documentElement()); |
| if (!documentElementUpdate) { |
| update.addElement(*document.documentElement(), nullptr, { RenderStyle::clonePtr(*documentElementStyle) }); |
| documentElementUpdate = update.elementUpdate(*document.documentElement()); |
| } |
| documentElementUpdate->style->setWritingMode(writingMode); |
| documentElementUpdate->style->setDirection(direction); |
| documentElementUpdate->changes.add(Change::Inherited); |
| } |
| } |
| |
| std::unique_ptr<RenderStyle> Adjuster::restoreUsedDocumentElementStyleToComputed(const RenderStyle& style) |
| { |
| if (style.writingMode().computedWritingMode() == Style::ComputedStyle::initialWritingMode() && style.writingMode().computedTextDirection() == Style::ComputedStyle::initialDirection()) |
| return { }; |
| |
| auto adjusted = RenderStyle::clonePtr(style); |
| if (!style.hasExplicitlySetWritingMode()) |
| adjusted->setWritingMode(Style::ComputedStyle::initialWritingMode()); |
| if (!style.hasExplicitlySetDirection()) |
| adjusted->setDirection(Style::ComputedStyle::initialDirection()); |
| |
| return adjusted; |
| } |
| |
| #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; |
| |
| Ref 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 (auto fixedLineHeight = lineHeight.tryFixed(); !fixedLineHeight || fixedLineHeight->resolveZoom(ZoomFactor { 1.0f }) >= 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, AdjustmentForTextAutosizing adjustment) |
| { |
| AutosizeStatus::updateStatus(style); |
| if (auto newFontSize = adjustment.newFontSize) { |
| auto fontDescription = style.fontDescription(); |
| fontDescription.setComputedSize(*newFontSize); |
| style.setFontDescription(WTF::move(fontDescription)); |
| } |
| if (auto newLineHeight = adjustment.newLineHeight) |
| style.setLineHeight(LineHeight::Fixed { *newLineHeight }); |
| if (auto newStatus = adjustment.newStatus) |
| style.setAutosizeStatus(*newStatus); |
| return adjustment.newFontSize || adjustment.newLineHeight; |
| } |
| |
| bool Adjuster::adjustForTextAutosizing(RenderStyle& style, const Element& element) |
| { |
| return adjustForTextAutosizing(style, adjustmentForTextAutosizing(style, element)); |
| } |
| #endif |
| |
| void Adjuster::adjustVisibilityForPseudoElement(RenderStyle& style, const Element& host) |
| { |
| if ((style.pseudoElementType() == PseudoElementType::After && host.visibilityAdjustment().contains(VisibilityAdjustment::AfterPseudo)) |
| || (style.pseudoElementType() == PseudoElementType::Before && host.visibilityAdjustment().contains(VisibilityAdjustment::BeforePseudo))) |
| style.setIsForceHidden(); |
| } |
| |
| } |
| } |