766 lines
34 KiB
C++
766 lines
34 KiB
C++
/*
|
||
* 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 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 "Element.h"
|
||
#include "EventNames.h"
|
||
#include "FrameView.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 "Page.h"
|
||
#include "Quirks.h"
|
||
#include "RenderBox.h"
|
||
#include "RenderStyle.h"
|
||
#include "RenderTheme.h"
|
||
#include "RuntimeEnabledFeatures.h"
|
||
#include "SVGElement.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, { });
|
||
if (auto* window = document.domWindow())
|
||
regionTypes.add(computeEventListenerRegionTypes(*window, { }));
|
||
|
||
rootStyle.setEventListenerRegionTypes(regionTypes);
|
||
}
|
||
|
||
OptionSet<EventListenerRegionType> Adjuster::computeEventListenerRegionTypes(const EventTarget& eventTarget, OptionSet<EventListenerRegionType> parentTypes)
|
||
{
|
||
#if ENABLE(WHEEL_EVENT_REGIONS)
|
||
if (!eventTarget.hasEventListeners())
|
||
return parentTypes;
|
||
|
||
auto types = parentTypes;
|
||
|
||
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);
|
||
};
|
||
|
||
findListeners(eventNames().wheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel);
|
||
findListeners(eventNames().mousewheelEvent, EventListenerRegionType::Wheel, EventListenerRegionType::NonPassiveWheel);
|
||
|
||
return types;
|
||
#else
|
||
UNUSED_PARAM(eventTarget);
|
||
UNUSED_PARAM(parentTypes);
|
||
return { };
|
||
#endif
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
// User agents are expected to have a rule in their user agent stylesheet that matches th elements that have a parent
|
||
// node whose computed value for the 'text-align' property is its initial value, whose declaration block consists of
|
||
// just a single declaration that sets the 'text-align' property to the value 'center'.
|
||
// https://html.spec.whatwg.org/multipage/rendering.html#rendering
|
||
if (m_element->hasTagName(thTag) && !style.hasExplicitlySetTextAlign() && m_parentStyle.textAlign() == RenderStyle::initialTextAlign())
|
||
style.setTextAlign(TextAlignMode::Center);
|
||
|
||
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
|
||
bool isInTopLayer = style.styleType() == PseudoId::Backdrop || (m_element && m_element->isInTopLayer());
|
||
if (style.position() != PositionType::Absolute && style.position() != PositionType::Fixed && isInTopLayer)
|
||
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());
|
||
|
||
// 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()
|
||
|| style.hasTransformRelatedProperty()
|
||
|| style.hasMask()
|
||
|| style.clipPath()
|
||
|| style.boxReflect()
|
||
|| style.hasFilter()
|
||
#if ENABLE(FILTERS_LEVEL_2)
|
||
|| style.hasBackdropFilter()
|
||
#endif
|
||
|| style.hasBlendMode()
|
||
|| style.hasIsolation()
|
||
|| style.position() == PositionType::Sticky
|
||
|| style.position() == PositionType::Fixed
|
||
|| style.willChangeCreatesStackingContext())
|
||
style.setUsedZIndex(0);
|
||
}
|
||
|
||
if (m_element) {
|
||
// 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());
|
||
}
|
||
|
||
// 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.textDecoration());
|
||
else
|
||
style.setTextDecorationsInEffect(style.textDecoration());
|
||
|
||
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);
|
||
}
|
||
|
||
// Menulists should have visible overflow
|
||
if (style.appearance() == MenulistPart) {
|
||
style.setOverflowX(Overflow::Visible);
|
||
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();
|
||
|
||
// FIXME: when dropping the -webkit prefix on transform-style, we should also have opacity < 1 cause flattening.
|
||
if (style.preserves3D() && (style.overflowX() != Overflow::Visible
|
||
|| style.overflowY() != Overflow::Visible
|
||
|| style.hasClip()
|
||
|| style.clipPath()
|
||
|| style.hasFilter()
|
||
#if ENABLE(FILTERS_LEVEL_2)
|
||
|| style.hasBackdropFilter()
|
||
#endif
|
||
|| style.hasBlendMode()))
|
||
style.setTransformStyle3D(TransformStyle3D::Flat);
|
||
|
||
if (is<SVGElement>(m_element))
|
||
adjustSVGElementStyle(style, downcast<SVGElement>(*m_element));
|
||
|
||
// If 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()));
|
||
|
||
if (m_element)
|
||
style.setEventListenerRegionTypes(computeEventListenerRegionTypes(*m_element, m_parentStyle.eventListenerRegionTypes()));
|
||
|
||
#if ENABLE(TEXT_AUTOSIZING)
|
||
if (m_element && m_document.settings().textAutosizingUsesIdempotentMode())
|
||
adjustForTextAutosizing(style, *m_element);
|
||
#endif
|
||
|
||
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 = style.styleType() == PseudoId::Backdrop || (m_element && m_element->isInTopLayer());
|
||
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());
|
||
|
||
// 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", AtomString::ConstructFromLiteral);
|
||
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", AtomString::ConstructFromLiteral);
|
||
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", AtomString::ConstructFromLiteral);
|
||
static MainThreadNeverDestroyed<const AtomString> class2("v-tree-select", AtomString::ConstructFromLiteral);
|
||
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", AtomString::ConstructFromLiteral);
|
||
static MainThreadNeverDestroyed<const AtomString> videoElementID("vjs_video_3_html5_api", AtomString::ConstructFromLiteral);
|
||
|
||
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
|
||
|
||
}
|
||
}
|