| /* |
| * 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/HTMLImageElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLPlugInElement.h" |
| #include "core/html/HTMLTableCellElement.h" |
| #include "core/html/HTMLTextAreaElement.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) { |
| switch (display) { |
| case EDisplay::Block: |
| case EDisplay::Table: |
| case EDisplay::WebkitBox: |
| case EDisplay::Flex: |
| case EDisplay::Grid: |
| case EDisplay::ListItem: |
| case EDisplay::FlowRoot: |
| return display; |
| case EDisplay::InlineTable: |
| return EDisplay::Table; |
| case EDisplay::WebkitInlineBox: |
| return EDisplay::WebkitBox; |
| case EDisplay::InlineFlex: |
| return EDisplay::Flex; |
| case EDisplay::InlineGrid: |
| return EDisplay::Grid; |
| |
| case EDisplay::Contents: |
| case EDisplay::Inline: |
| case EDisplay::InlineBlock: |
| case EDisplay::TableRowGroup: |
| case EDisplay::TableHeaderGroup: |
| case EDisplay::TableFooterGroup: |
| case EDisplay::TableRow: |
| case EDisplay::TableColumnGroup: |
| case EDisplay::TableColumn: |
| case EDisplay::TableCell: |
| case EDisplay::TableCaption: |
| return EDisplay::Block; |
| case EDisplay::None: |
| ASSERT_NOT_REACHED(); |
| return display; |
| } |
| ASSERT_NOT_REACHED(); |
| return EDisplay::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() == EDisplay::InlineTable || |
| style.display() == EDisplay::InlineBlock || |
| style.display() == EDisplay::WebkitInlineBox || |
| isAtShadowBoundary(element) || style.isFloating() || |
| style.hasOutOfFlowPosition() || isOutermostSVGElement(element) || |
| isHTMLRTElement(element); |
| } |
| |
| // Certain elements (<a>, <font>) override text decoration colors. "The font |
| // element is expected to override the color of any text decoration that spans |
| // the text of the element to the used value of the element's 'color' property." |
| // (https://html.spec.whatwg.org/multipage/rendering.html#phrasing-content-3) |
| // The <a> behavior is non-standard. |
| static bool overridesTextDecorationColors(const Element* element) { |
| return element && |
| (isHTMLFontElement(element) || isHTMLAnchorElement(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() == PseudoIdBackdrop; |
| } |
| |
| static bool layoutParentStyleForcesZIndexToCreateStackingContext( |
| const ComputedStyle& layoutParentStyle) { |
| return layoutParentStyle.isDisplayFlexibleOrGridBox(); |
| } |
| |
| void StyleAdjuster::adjustStyleForEditing(ComputedStyle& style) { |
| if (style.userModify() != READ_WRITE_PLAINTEXT_ONLY) |
| return; |
| // Collapsing whitespace is harmful in plain-text editing. |
| if (style.whiteSpace() == EWhiteSpace::kNormal) |
| style.setWhiteSpace(EWhiteSpace::kPreWrap); |
| else if (style.whiteSpace() == EWhiteSpace::kNowrap) |
| style.setWhiteSpace(EWhiteSpace::kPre); |
| else if (style.whiteSpace() == EWhiteSpace::kPreLine) |
| style.setWhiteSpace(EWhiteSpace::kPreWrap); |
| } |
| |
| static void adjustStyleForFirstLetter(ComputedStyle& style) { |
| if (style.styleType() != PseudoIdFirstLetter) |
| return; |
| |
| // Force inline display (except for floating first-letters). |
| style.setDisplay(style.isFloating() ? EDisplay::Block : EDisplay::Inline); |
| |
| // CSS2 says first-letter can't be positioned. |
| style.setPosition(EPosition::kStatic); |
| } |
| |
| void StyleAdjuster::adjustStyleForAlignment(ComputedStyle& style, |
| const ComputedStyle& parentStyle) { |
| // To avoid needing to copy the RareNonInheritedData, we repurpose the 'auto' |
| // flag to not just mean 'auto' prior to running the StyleAdjuster but also |
| // mean 'normal' after running it. |
| |
| // If the inherited value of justify-items includes the 'legacy' keyword, |
| // 'auto' computes to the the inherited value. Otherwise, 'auto' computes to |
| // 'normal'. |
| if (style.justifyItemsPosition() == ItemPositionAuto) { |
| if (parentStyle.justifyItemsPositionType() == LegacyPosition) |
| style.setJustifyItems(parentStyle.justifyItems()); |
| } |
| |
| // The 'auto' keyword computes the computed value of justify-items on the |
| // parent (minus any legacy keywords), or 'normal' if the box has no parent. |
| if (style.justifySelfPosition() == ItemPositionAuto) { |
| if (parentStyle.justifyItemsPositionType() == LegacyPosition) |
| style.setJustifySelfPosition(parentStyle.justifyItemsPosition()); |
| else if (parentStyle.justifyItemsPosition() != ItemPositionAuto) |
| style.setJustifySelf(parentStyle.justifyItems()); |
| } |
| |
| // The 'auto' keyword computes the computed value of align-items on the parent |
| // or 'normal' if the box has no parent. |
| if (style.alignSelfPosition() == ItemPositionAuto && |
| parentStyle.alignItemsPosition() != |
| ComputedStyle::initialDefaultAlignment().position()) |
| style.setAlignSelf(parentStyle.alignItems()); |
| } |
| |
| static void adjustStyleForHTMLElement(ComputedStyle& style, |
| 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)) { |
| if (style.whiteSpace() == EWhiteSpace::kWebkitNowrap) { |
| // 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(EWhiteSpace::kNormal); |
| else |
| style.setWhiteSpace(EWhiteSpace::kNowrap); |
| } |
| return; |
| } |
| |
| if (isHTMLImageElement(element)) { |
| if (toHTMLImageElement(element).isCollapsed()) |
| style.setDisplay(EDisplay::None); |
| return; |
| } |
| |
| if (isHTMLTableElement(element)) { |
| // Tables never support the -webkit-* values for text-align and will reset |
| // back to the default. |
| if (style.textAlign() == ETextAlign::kWebkitLeft || |
| style.textAlign() == ETextAlign::kWebkitCenter || |
| style.textAlign() == ETextAlign::kWebkitRight) |
| style.setTextAlign(ETextAlign::kStart); |
| 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(EPosition::kStatic); |
| style.setDisplay(EDisplay::Block); |
| return; |
| } |
| |
| if (isHTMLFrameElementBase(element)) { |
| // Frames cannot overflow (they are always the size we ask them to be). |
| // Some compositing code paths may try to draw scrollbars anyhow. |
| style.setOverflowX(EOverflow::kVisible); |
| style.setOverflowY(EOverflow::kVisible); |
| return; |
| } |
| |
| if (isHTMLRTElement(element)) { |
| // Ruby text does not support float or position. This might change with |
| // evolution of the specification. |
| style.setPosition(EPosition::kStatic); |
| style.setFloating(EFloat::kNone); |
| return; |
| } |
| |
| if (isHTMLLegendElement(element)) { |
| style.setDisplay(EDisplay::Block); |
| return; |
| } |
| |
| if (isHTMLMarqueeElement(element)) { |
| // For now, <marquee> requires an overflow clip to work properly. |
| style.setOverflowX(EOverflow::kHidden); |
| style.setOverflowY(EOverflow::kHidden); |
| return; |
| } |
| |
| if (isHTMLTextAreaElement(element)) { |
| // Textarea considers overflow visible as auto. |
| style.setOverflowX(style.overflowX() == EOverflow::kVisible |
| ? EOverflow::kAuto |
| : style.overflowX()); |
| style.setOverflowY(style.overflowY() == EOverflow::kVisible |
| ? EOverflow::kAuto |
| : style.overflowY()); |
| return; |
| } |
| |
| if (isHTMLPlugInElement(element)) { |
| style.setRequiresAcceleratedCompositingForExternalReasons( |
| toHTMLPlugInElement(element).shouldAccelerate()); |
| return; |
| } |
| } |
| |
| static void adjustOverflow(ComputedStyle& style) { |
| DCHECK(style.overflowX() != EOverflow::kVisible || |
| style.overflowY() != EOverflow::kVisible); |
| |
| if (style.display() == EDisplay::Table || |
| style.display() == EDisplay::InlineTable) { |
| // 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() != EOverflow::kHidden) |
| style.setOverflowX(EOverflow::kVisible); |
| if (style.overflowY() != EOverflow::kHidden) |
| style.setOverflowY(EOverflow::kVisible); |
| // If we are left with conflicting overflow values for the x and y axes on a |
| // table then resolve both to OverflowVisible. This is interoperable |
| // behaviour but is not specced anywhere. |
| if (style.overflowX() == EOverflow::kVisible) |
| style.setOverflowY(EOverflow::kVisible); |
| else if (style.overflowY() == EOverflow::kVisible) |
| style.setOverflowX(EOverflow::kVisible); |
| } else if (style.overflowX() == EOverflow::kVisible && |
| style.overflowY() != EOverflow::kVisible) { |
| // 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(EOverflow::kAuto); |
| } else if (style.overflowY() == EOverflow::kVisible && |
| style.overflowX() != EOverflow::kVisible) { |
| style.setOverflowY(EOverflow::kAuto); |
| } |
| |
| // Menulists should have visible overflow |
| if (style.appearance() == MenulistPart) { |
| style.setOverflowX(EOverflow::kVisible); |
| style.setOverflowY(EOverflow::kVisible); |
| } |
| } |
| |
| static void adjustStyleForDisplay(ComputedStyle& style, |
| const ComputedStyle& layoutParentStyle, |
| Document* document) { |
| if (style.display() == EDisplay::Block && !style.isFloating()) |
| return; |
| |
| if (style.display() == EDisplay::Contents) |
| 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() == EDisplay::Inline && |
| style.styleType() == PseudoIdNone && |
| style.getWritingMode() != layoutParentStyle.getWritingMode()) |
| style.setDisplay(EDisplay::InlineBlock); |
| |
| // After performing the display mutation, check table rows. We do not honor |
| // position: relative table rows. This has been established for position: |
| // relative in CSS2.1 (and caused a crash in containingBlock() on some sites). |
| if ((style.display() == EDisplay::TableHeaderGroup || |
| style.display() == EDisplay::TableRowGroup || |
| style.display() == EDisplay::TableFooterGroup || |
| style.display() == EDisplay::TableRow) && |
| style.position() == EPosition::kRelative) |
| style.setPosition(EPosition::kStatic); |
| |
| // Cannot support position: sticky for table columns and column groups because |
| // current code is only doing background painting through columns / column |
| // groups. |
| if ((style.display() == EDisplay::TableColumnGroup || |
| style.display() == EDisplay::TableColumn) && |
| style.position() == EPosition::kSticky) |
| style.setPosition(EPosition::kStatic); |
| |
| // 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() == EDisplay::TableColumn || |
| style.display() == EDisplay::TableColumnGroup || |
| style.display() == EDisplay::TableFooterGroup || |
| style.display() == EDisplay::TableHeaderGroup || |
| style.display() == EDisplay::TableRow || |
| style.display() == EDisplay::TableRowGroup || |
| style.display() == EDisplay::TableCell) |
| style.setWritingMode(layoutParentStyle.getWritingMode()); |
| |
| // 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.getWritingMode() != WritingMode::kHorizontalTb && |
| (style.display() == EDisplay::WebkitBox || |
| style.display() == EDisplay::WebkitInlineBox)) |
| style.setWritingMode(WritingMode::kHorizontalTb); |
| |
| if (layoutParentStyle.isDisplayFlexibleOrGridBox()) { |
| style.setFloating(EFloat::kNone); |
| style.setDisplay(equivalentBlockDisplay(style.display())); |
| |
| // 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().isPercentOrCalc() || |
| style.paddingAfter().isPercentOrCalc()) |
| UseCounter::count(document, UseCounter::FlexboxPercentagePaddingVertical); |
| if (style.marginBefore().isPercentOrCalc() || |
| style.marginAfter().isPercentOrCalc()) |
| UseCounter::count(document, UseCounter::FlexboxPercentageMarginVertical); |
| } |
| } |
| |
| void StyleAdjuster::adjustComputedStyle(ComputedStyle& style, |
| const ComputedStyle& parentStyle, |
| const ComputedStyle& layoutParentStyle, |
| Element* element) { |
| if (style.display() != EDisplay::None) { |
| if (element && element->isHTMLElement()) |
| adjustStyleForHTMLElement(style, toHTMLElement(*element)); |
| |
| // Per the spec, position 'static' and 'relative' in the top layer compute |
| // to 'absolute'. |
| if (isInTopLayer(element, style) && |
| (style.position() == EPosition::kStatic || |
| style.position() == EPosition::kRelative)) |
| style.setPosition(EPosition::kAbsolute); |
| |
| // Absolute/fixed positioned elements, floating elements and the document |
| // element need block-like outside display. |
| if (style.display() != EDisplay::Contents && |
| (style.hasOutOfFlowPosition() || style.isFloating())) |
| style.setDisplay(equivalentBlockDisplay(style.display())); |
| |
| if (element && element->document().documentElement() == element) |
| style.setDisplay(equivalentBlockDisplay(style.display())); |
| |
| // We don't adjust the first letter style earlier because we may change the |
| // display setting in adjustStyeForTagName() above. |
| adjustStyleForFirstLetter(style); |
| |
| adjustStyleForDisplay(style, layoutParentStyle, |
| element ? &element->document() : 0); |
| |
| // Paint containment forces a block formatting context, so we must coerce |
| // from inline. https://drafts.csswg.org/css-containment/#containment-paint |
| if (style.containsPaint() && style.display() == EDisplay::Inline) |
| style.setDisplay(EDisplay::Block); |
| } 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() == EPosition::kStatic && |
| !layoutParentStyleForcesZIndexToCreateStackingContext( |
| layoutParentStyle)) { |
| style.setIsStackingContext(false); |
| // TODO(alancutter): Avoid altering z-index here. |
| if (!style.hasAutoZIndex()) |
| style.setZIndex(0); |
| } else if (!style.hasAutoZIndex()) { |
| style.setIsStackingContext(true); |
| } |
| |
| if (style.overflowX() != EOverflow::kVisible || |
| style.overflowY() != EOverflow::kVisible) |
| adjustOverflow(style); |
| |
| if (doesNotInheritTextDecoration(style, element)) |
| style.clearAppliedTextDecorations(); |
| else |
| style.restoreParentTextDecorations(parentStyle); |
| style.applyTextDecorations( |
| parentStyle.visitedDependentColor(CSSPropertyTextDecorationColor), |
| overridesTextDecorationColors(element)); |
| |
| // 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(PseudoIdFirstLetter) || style.transitions() || |
| style.animations()) |
| style.setUnique(); |
| |
| adjustStyleForEditing(style); |
| |
| bool isSVGElement = element && element->isSVGElement(); |
| if (isSVGElement) { |
| // display: contents computes to inline for replaced elements and form |
| // controls, and isn't specified for other kinds of SVG content[1], so let's |
| // just do the same here for all other SVG elements. |
| // |
| // If we wouldn't do this, then we'd need to ensure that display: contents |
| // doesn't prevent SVG elements from generating a LayoutObject in |
| // SVGElement::layoutObjectIsNeeded. |
| // |
| // [1]: https://www.w3.org/TR/SVG/painting.html#DisplayProperty |
| if (style.display() == EDisplay::Contents) |
| style.setDisplay(EDisplay::Inline); |
| |
| // 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(EDisplay::Block); |
| |
| // Columns don't apply to svg text elements. |
| if (isSVGTextElement(*element)) |
| style.clearMultiCol(); |
| } |
| adjustStyleForAlignment(style, parentStyle); |
| } |
| |
| } // namespace blink |