blob: afac17c6d9cca4d42ec5d51f8ca1b88f81b45297 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
* Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
* Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 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) 2013 Google Inc. All rights reserved.
*
* 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 "core/css/resolver/StyleAdjuster.h"
#include "core/HTMLNames.h"
#include "core/SVGNames.h"
#include "core/dom/ContainerNode.h"
#include "core/dom/Document.h"
#include "core/dom/Element.h"
#include "core/frame/FrameView.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/html/HTMLIFrameElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/HTMLTableCellElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/layout/LayoutReplaced.h"
#include "core/layout/LayoutTheme.h"
#include "core/style/ComputedStyle.h"
#include "core/style/ComputedStyleConstants.h"
#include "core/svg/SVGSVGElement.h"
#include "platform/Length.h"
#include "platform/transforms/TransformOperations.h"
#include "wtf/Assertions.h"
namespace blink {
using namespace HTMLNames;
static EDisplay equivalentBlockDisplay(EDisplay display, bool isFloating, bool strictParsing)
{
switch (display) {
case BLOCK:
case TABLE:
case BOX:
case FLEX:
case GRID:
return display;
case LIST_ITEM:
// It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, but only in quirks mode.
if (!strictParsing && isFloating)
return BLOCK;
return display;
case INLINE_TABLE:
return TABLE;
case INLINE_BOX:
return BOX;
case INLINE_FLEX:
return FLEX;
case INLINE_GRID:
return GRID;
case INLINE:
case INLINE_BLOCK:
case TABLE_ROW_GROUP:
case TABLE_HEADER_GROUP:
case TABLE_FOOTER_GROUP:
case TABLE_ROW:
case TABLE_COLUMN_GROUP:
case TABLE_COLUMN:
case TABLE_CELL:
case TABLE_CAPTION:
return BLOCK;
case NONE:
ASSERT_NOT_REACHED();
return NONE;
}
ASSERT_NOT_REACHED();
return BLOCK;
}
static bool isOutermostSVGElement(const Element* element)
{
return element && element->isSVGElement() && toSVGElement(*element).isOutermostSVGSVGElement();
}
// CSS requires text-decoration to be reset at each DOM element for
// inline blocks, inline tables, shadow DOM crossings, floating elements,
// and absolute or relatively positioned elements. Outermost <svg> roots are
// considered to be atomic inline-level.
static bool doesNotInheritTextDecoration(const ComputedStyle& style, const Element* element)
{
return style.display() == INLINE_TABLE
|| style.display() == INLINE_BLOCK || style.display() == INLINE_BOX || isAtShadowBoundary(element)
|| style.isFloating() || style.hasOutOfFlowPosition() || isOutermostSVGElement(element);
}
// FIXME: This helper is only needed because pseudoStyleForElement passes a null
// element to adjustComputedStyle, so we can't just use element->isInTopLayer().
static bool isInTopLayer(const Element* element, const ComputedStyle& style)
{
return (element && element->isInTopLayer()) || style.styleType() == BACKDROP;
}
static bool parentStyleForcesZIndexToCreateStackingContext(const ComputedStyle& parentStyle)
{
return parentStyle.isDisplayFlexibleOrGridBox();
}
static bool hasWillChangeThatCreatesStackingContext(const ComputedStyle& style)
{
for (size_t i = 0; i < style.willChangeProperties().size(); ++i) {
switch (style.willChangeProperties()[i]) {
case CSSPropertyOpacity:
case CSSPropertyTransform:
case CSSPropertyAliasWebkitTransform:
case CSSPropertyTransformStyle:
case CSSPropertyAliasWebkitTransformStyle:
case CSSPropertyPerspective:
case CSSPropertyAliasWebkitPerspective:
case CSSPropertyWebkitMask:
case CSSPropertyWebkitMaskBoxImage:
case CSSPropertyWebkitClipPath:
case CSSPropertyWebkitBoxReflect:
case CSSPropertyWebkitFilter:
case CSSPropertyBackdropFilter:
case CSSPropertyZIndex:
case CSSPropertyPosition:
return true;
case CSSPropertyMixBlendMode:
case CSSPropertyIsolation:
if (RuntimeEnabledFeatures::cssCompositingEnabled())
return true;
break;
default:
break;
}
}
return false;
}
void StyleAdjuster::adjustComputedStyle(ComputedStyle& style, const ComputedStyle& parentStyle, Element* element)
{
if (style.display() != NONE) {
if (element && element->isHTMLElement())
adjustStyleForHTMLElement(style, parentStyle, toHTMLElement(*element));
// Per the spec, position 'static' and 'relative' in the top layer compute to 'absolute'.
if (isInTopLayer(element, style) && (style.position() == StaticPosition || style.position() == RelativePosition))
style.setPosition(AbsolutePosition);
// Absolute/fixed positioned elements, floating elements and the document element need block-like outside display.
if (style.hasOutOfFlowPosition() || style.isFloating() || (element && element->document().documentElement() == element))
style.setDisplay(equivalentBlockDisplay(style.display(), style.isFloating(), !m_useQuirksModeStyles));
// We don't adjust the first letter style earlier because we may change the display setting in
// adjustStyeForTagName() above.
adjustStyleForFirstLetter(style);
adjustStyleForDisplay(style, parentStyle, element ? &element->document() : 0);
} else {
adjustStyleForFirstLetter(style);
}
if (element && element->hasCompositorProxy())
style.setHasCompositorProxy(true);
// Make sure our z-index value is only applied if the object is positioned.
if (style.position() == StaticPosition && !parentStyleForcesZIndexToCreateStackingContext(parentStyle))
style.setHasAutoZIndex();
// 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.hasAutoZIndex() && ((element && element->document().documentElement() == element)
|| style.hasOpacity()
|| style.hasTransformRelatedProperty()
|| style.hasMask()
|| style.clipPath()
|| style.boxReflect()
|| style.hasFilter()
|| style.hasBlendMode()
|| style.hasIsolation()
|| style.position() == FixedPosition
|| isInTopLayer(element, style)
|| hasWillChangeThatCreatesStackingContext(style)
|| style.containsPaint()))
style.setZIndex(0);
if (doesNotInheritTextDecoration(style, element))
style.clearAppliedTextDecorations();
style.applyTextDecorations();
if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE)
adjustOverflow(style);
// Cull out any useless layers and also repeat patterns into additional layers.
style.adjustBackgroundLayers();
style.adjustMaskLayers();
// Let the theme also have a crack at adjusting the style.
if (style.hasAppearance())
LayoutTheme::theme().adjustStyle(style, element);
// If we have first-letter pseudo style, transitions, or animations, do not share this style.
if (style.hasPseudoStyle(FIRST_LETTER) || style.transitions() || style.animations())
style.setUnique();
// FIXME: when dropping the -webkit prefix on transform-style, we should also have opacity < 1 cause flattening.
if (style.preserves3D() && (style.overflowX() != OVISIBLE
|| style.overflowY() != OVISIBLE
|| style.hasFilter()))
style.setTransformStyle3D(TransformStyle3DFlat);
bool isSVGElement = element && element->isSVGElement();
if (isSVGElement) {
// Only the root <svg> element in an SVG document fragment tree honors css position
if (!(isSVGSVGElement(*element) && element->parentNode() && !element->parentNode()->isSVGElement()))
style.setPosition(ComputedStyle::initialPosition());
// SVG text layout code expects us to be a block-level style element.
if ((isSVGForeignObjectElement(*element) || isSVGTextElement(*element)) && style.isDisplayInlineType())
style.setDisplay(BLOCK);
// Columns don't apply to svg text elements.
if (isSVGTextElement(*element))
style.clearMultiCol();
}
adjustStyleForAlignment(style, parentStyle);
}
void StyleAdjuster::adjustStyleForFirstLetter(ComputedStyle& style)
{
if (style.styleType() != FIRST_LETTER)
return;
// Force inline display (except for floating first-letters).
style.setDisplay(style.isFloating() ? BLOCK : INLINE);
// CSS2 says first-letter can't be positioned.
style.setPosition(StaticPosition);
}
void StyleAdjuster::adjustStyleForAlignment(ComputedStyle& style, const ComputedStyle& parentStyle)
{
bool isFlexOrGrid = style.isDisplayFlexibleOrGridBox();
bool absolutePositioned = style.position() == AbsolutePosition;
// If the inherited value of justify-items includes the legacy keyword, 'auto'
// computes to the the inherited value.
// Otherwise, auto computes to:
// - 'stretch' for flex containers and grid containers.
// - 'start' for everything else.
if (style.justifyItemsPosition() == ItemPositionAuto) {
if (parentStyle.justifyItemsPositionType() == LegacyPosition)
style.setJustifyItems(parentStyle.justifyItems());
else if (isFlexOrGrid)
style.setJustifyItemsPosition(ItemPositionStretch);
}
// The 'auto' keyword computes to 'stretch' on absolutely-positioned elements,
// and to the computed value of justify-items on the parent (minus
// any legacy keywords) on all other boxes.
if (style.justifySelfPosition() == ItemPositionAuto) {
if (absolutePositioned)
style.setJustifySelfPosition(ItemPositionStretch);
else
style.setJustifySelf(parentStyle.justifyItems());
}
// The 'auto' keyword computes to:
// - 'stretch' for flex containers and grid containers,
// - 'start' for everything else.
if (style.alignItemsPosition() == ItemPositionAuto) {
if (isFlexOrGrid)
style.setAlignItemsPosition(ItemPositionStretch);
}
// The 'auto' keyword computes to 'stretch' on absolutely-positioned elements,
// and to the computed value of align-items on the parent (minus
// any 'legacy' keywords) on all other boxes.
if (style.alignSelfPosition() == ItemPositionAuto) {
if (absolutePositioned)
style.setAlignSelfPosition(ItemPositionStretch);
else
style.setAlignSelf(parentStyle.alignItems());
}
// Block Containers: For table cells, the behavior of the 'auto' depends on the computed
// value of 'vertical-align', otherwise behaves as 'start'.
// Flex Containers: 'auto' computes to 'flex-start'.
// Grid Containers: 'auto' computes to 'start', and 'stretch' behaves like 'start'.
if ((style.justifyContentPosition() == ContentPositionAuto) && (style.justifyContentDistribution() == ContentDistributionDefault)) {
if (style.isDisplayFlexibleOrGridBox()) {
if (style.isDisplayFlexibleBox())
style.setJustifyContentPosition(ContentPositionFlexStart);
else
style.setJustifyContentPosition(ContentPositionStart);
}
}
// Block Containers: For table cells, the behavior of the 'auto' depends on the computed
// value of 'vertical-align', otherwise behaves as 'start'.
// Flex Containers: 'auto' computes to 'stretch'.
// Grid Containers: 'auto' computes to 'start', and 'stretch' behaves like 'start'.
if (style.alignContentPosition() == ContentPositionAuto && style.alignContentDistribution() == ContentDistributionDefault) {
if (style.isDisplayFlexibleOrGridBox()) {
if (style.isDisplayFlexibleBox())
style.setAlignContentDistribution(ContentDistributionStretch);
else
style.setAlignContentPosition(ContentPositionStart);
}
}
}
void StyleAdjuster::adjustStyleForHTMLElement(ComputedStyle& style, const ComputedStyle& parentStyle, HTMLElement& element)
{
// <div> and <span> are the most common elements on the web, we skip all the work for them.
if (isHTMLDivElement(element) || isHTMLSpanElement(element))
return;
if (isHTMLTableCellElement(element)) {
// FIXME: We shouldn't be overriding start/-webkit-auto like this. Do it in html.css instead.
// Table headers with a text-align of -webkit-auto will change the text-align to center.
if (element.hasTagName(thTag) && style.textAlign() == TASTART)
style.setTextAlign(CENTER);
if (style.whiteSpace() == KHTML_NOWRAP) {
// 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 NOWRAP.
if (style.width().isFixed())
style.setWhiteSpace(NORMAL);
else
style.setWhiteSpace(NOWRAP);
}
return;
}
if (isHTMLTableElement(element)) {
// Tables never support the -webkit-* values for text-align and will reset back to the default.
if (style.textAlign() == WEBKIT_LEFT || style.textAlign() == WEBKIT_CENTER || style.textAlign() == WEBKIT_RIGHT)
style.setTextAlign(TASTART);
return;
}
if (isHTMLFrameElement(element) || isHTMLFrameSetElement(element)) {
// 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.
style.setPosition(StaticPosition);
style.setDisplay(BLOCK);
return;
}
if (isHTMLRTElement(element)) {
// Ruby text does not support float or position. This might change with evolution of the specification.
style.setPosition(StaticPosition);
style.setFloating(NoFloat);
return;
}
if (isHTMLMarqueeElement(element)) {
// For now, <marquee> requires an overflow clip to work properly.
style.setOverflowX(OHIDDEN);
style.setOverflowY(OHIDDEN);
return;
}
if (isHTMLTextAreaElement(element)) {
// Textarea considers overflow visible as auto.
style.setOverflowX(style.overflowX() == OVISIBLE ? OAUTO : style.overflowX());
style.setOverflowY(style.overflowY() == OVISIBLE ? OAUTO : style.overflowY());
return;
}
if (isHTMLPlugInElement(element)) {
style.setRequiresAcceleratedCompositingForExternalReasons(toHTMLPlugInElement(element).shouldAccelerate());
return;
}
}
void StyleAdjuster::adjustOverflow(ComputedStyle& style)
{
ASSERT(style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE);
if (style.display() == TABLE || style.display() == INLINE_TABLE) {
// Tables only support overflow:hidden and overflow:visible and ignore anything else,
// see http://dev.w3.org/csswg/css2/visufx.html#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() != OHIDDEN)
style.setOverflowX(OVISIBLE);
if (style.overflowY() != OHIDDEN)
style.setOverflowY(OVISIBLE);
// If we are left with conflicting overflow values for the x and y axes on a table then resolve
// both to OVISIBLE. This is interoperable behaviour but is not specced anywhere.
if (style.overflowX() == OVISIBLE)
style.setOverflowY(OVISIBLE);
else if (style.overflowY() == OVISIBLE)
style.setOverflowX(OVISIBLE);
} else if (style.overflowX() == OVISIBLE && style.overflowY() != OVISIBLE) {
// If either overflow value is not visible, change to auto.
// 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.
style.setOverflowX(OAUTO);
} else if (style.overflowY() == OVISIBLE && style.overflowX() != OVISIBLE) {
style.setOverflowY(OAUTO);
}
// Menulists should have visible overflow
if (style.appearance() == MenulistPart) {
style.setOverflowX(OVISIBLE);
style.setOverflowY(OVISIBLE);
}
}
void StyleAdjuster::adjustStyleForDisplay(ComputedStyle& style, const ComputedStyle& parentStyle, Document* document)
{
if (style.display() == BLOCK && !style.isFloating())
return;
// 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() == INLINE && style.styleType() == NOPSEUDO && style.writingMode() != parentStyle.writingMode())
style.setDisplay(INLINE_BLOCK);
// After performing the display mutation, check table rows. We do not honor position: relative 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() == TABLE_HEADER_GROUP || style.display() == TABLE_ROW_GROUP
|| style.display() == TABLE_FOOTER_GROUP || style.display() == TABLE_ROW)
&& style.position() == RelativePosition)
style.setPosition(StaticPosition);
// 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() == TABLE_COLUMN || style.display() == TABLE_COLUMN_GROUP || style.display() == TABLE_FOOTER_GROUP
|| style.display() == TABLE_HEADER_GROUP || style.display() == TABLE_ROW || style.display() == TABLE_ROW_GROUP
|| style.display() == TABLE_CELL)
style.setWritingMode(parentStyle.writingMode());
// FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting
// of block-flow to anything other than TopToBottomWritingMode.
// https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support.
if (style.writingMode() != TopToBottomWritingMode && (style.display() == BOX || style.display() == INLINE_BOX))
style.setWritingMode(TopToBottomWritingMode);
if (parentStyle.isDisplayFlexibleOrGridBox()) {
style.setFloating(NoFloat);
style.setDisplay(equivalentBlockDisplay(style.display(), style.isFloating(), !m_useQuirksModeStyles));
// We want to count vertical percentage paddings/margins on flex items because our current
// behavior is different from the spec and we want to gather compatibility data.
if (style.paddingBefore().hasPercent() || style.paddingAfter().hasPercent())
UseCounter::count(document, UseCounter::FlexboxPercentagePaddingVertical);
if (style.marginBefore().hasPercent() || style.marginAfter().hasPercent())
UseCounter::count(document, UseCounter::FlexboxPercentageMarginVertical);
}
}
}