1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com)
4 * Copyright (C) 2006, 2007 Nicholas Shanks (webkit@nickshanks.com)
5 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.
6 * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
7 * Copyright (C) 2007, 2008 Eric Seidel <eric@webkit.org>
8 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
9 * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
10 * Copyright (C) Research In Motion Limited 2011. All rights reserved.
11 * Copyright (C) 2013 Google Inc. All rights reserved.
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Library General Public
15 * License as published by the Free Software Foundation; either
16 * version 2 of the License, or (at your option) any later version.
17 *
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 * Library General Public License for more details.
22 *
23 * You should have received a copy of the GNU Library General Public License
24 * along with this library; see the file COPYING.LIB.  If not, write to
25 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26 * Boston, MA 02110-1301, USA.
27 */
28
29#include "config.h"
30#include "core/css/resolver/StyleAdjuster.h"
31
32#include "core/HTMLNames.h"
33#include "core/SVGNames.h"
34#include "core/dom/ContainerNode.h"
35#include "core/dom/Document.h"
36#include "core/dom/Element.h"
37#include "core/dom/NodeRenderStyle.h"
38#include "core/html/HTMLIFrameElement.h"
39#include "core/html/HTMLInputElement.h"
40#include "core/html/HTMLPlugInElement.h"
41#include "core/html/HTMLTableCellElement.h"
42#include "core/html/HTMLTextAreaElement.h"
43#include "core/frame/FrameView.h"
44#include "core/frame/Settings.h"
45#include "core/rendering/RenderReplaced.h"
46#include "core/rendering/RenderTheme.h"
47#include "core/rendering/style/GridPosition.h"
48#include "core/rendering/style/RenderStyle.h"
49#include "core/rendering/style/RenderStyleConstants.h"
50#include "core/svg/SVGSVGElement.h"
51#include "platform/Length.h"
52#include "platform/transforms/TransformOperations.h"
53#include "wtf/Assertions.h"
54
55namespace blink {
56
57using namespace HTMLNames;
58
59static EDisplay equivalentBlockDisplay(EDisplay display, bool isFloating, bool strictParsing)
60{
61    switch (display) {
62    case BLOCK:
63    case TABLE:
64    case BOX:
65    case FLEX:
66    case GRID:
67        return display;
68
69    case LIST_ITEM:
70        // It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, but only in quirks mode.
71        if (!strictParsing && isFloating)
72            return BLOCK;
73        return display;
74    case INLINE_TABLE:
75        return TABLE;
76    case INLINE_BOX:
77        return BOX;
78    case INLINE_FLEX:
79        return FLEX;
80    case INLINE_GRID:
81        return GRID;
82
83    case INLINE:
84    case INLINE_BLOCK:
85    case TABLE_ROW_GROUP:
86    case TABLE_HEADER_GROUP:
87    case TABLE_FOOTER_GROUP:
88    case TABLE_ROW:
89    case TABLE_COLUMN_GROUP:
90    case TABLE_COLUMN:
91    case TABLE_CELL:
92    case TABLE_CAPTION:
93        return BLOCK;
94    case NONE:
95        ASSERT_NOT_REACHED();
96        return NONE;
97    }
98    ASSERT_NOT_REACHED();
99    return BLOCK;
100}
101
102// CSS requires text-decoration to be reset at each DOM element for tables,
103// inline blocks, inline tables, shadow DOM crossings, floating elements,
104// and absolute or relatively positioned elements.
105static bool doesNotInheritTextDecoration(const RenderStyle* style, const Element* e)
106{
107    return style->display() == TABLE || style->display() == INLINE_TABLE
108        || style->display() == INLINE_BLOCK || style->display() == INLINE_BOX || isAtShadowBoundary(e)
109        || style->isFloating() || style->hasOutOfFlowPosition();
110}
111
112// FIXME: This helper is only needed because pseudoStyleForElement passes a null
113// element to adjustRenderStyle, so we can't just use element->isInTopLayer().
114static bool isInTopLayer(const Element* element, const RenderStyle* style)
115{
116    return (element && element->isInTopLayer()) || (style && style->styleType() == BACKDROP);
117}
118
119static bool parentStyleForcesZIndexToCreateStackingContext(const RenderStyle* parentStyle)
120{
121    return parentStyle->isDisplayFlexibleOrGridBox();
122}
123
124static bool hasWillChangeThatCreatesStackingContext(const RenderStyle* style)
125{
126    for (size_t i = 0; i < style->willChangeProperties().size(); ++i) {
127        switch (style->willChangeProperties()[i]) {
128        case CSSPropertyOpacity:
129        case CSSPropertyTransform:
130        case CSSPropertyWebkitTransform:
131        case CSSPropertyTransformStyle:
132        case CSSPropertyWebkitTransformStyle:
133        case CSSPropertyPerspective:
134        case CSSPropertyWebkitPerspective:
135        case CSSPropertyWebkitMask:
136        case CSSPropertyWebkitMaskBoxImage:
137        case CSSPropertyWebkitClipPath:
138        case CSSPropertyWebkitBoxReflect:
139        case CSSPropertyWebkitFilter:
140        case CSSPropertyZIndex:
141        case CSSPropertyPosition:
142            return true;
143        case CSSPropertyMixBlendMode:
144        case CSSPropertyIsolation:
145            if (RuntimeEnabledFeatures::cssCompositingEnabled())
146                return true;
147            break;
148        default:
149            break;
150        }
151    }
152    return false;
153}
154
155void StyleAdjuster::adjustRenderStyle(RenderStyle* style, RenderStyle* parentStyle, Element *e, const CachedUAStyle* cachedUAStyle)
156{
157    ASSERT(parentStyle);
158
159    if (style->display() != NONE) {
160        if (e)
161            adjustStyleForTagName(style, parentStyle, *e);
162
163        // Per the spec, position 'static' and 'relative' in the top layer compute to 'absolute'.
164        if (isInTopLayer(e, style) && (style->position() == StaticPosition || style->position() == RelativePosition))
165            style->setPosition(AbsolutePosition);
166
167        // Absolute/fixed positioned elements, floating elements and the document element need block-like outside display.
168        if (style->hasOutOfFlowPosition() || style->isFloating() || (e && e->document().documentElement() == e))
169            style->setDisplay(equivalentBlockDisplay(style->display(), style->isFloating(), !m_useQuirksModeStyles));
170
171        adjustStyleForDisplay(style, parentStyle);
172    }
173
174    // Make sure our z-index value is only applied if the object is positioned.
175    if (style->position() == StaticPosition && !parentStyleForcesZIndexToCreateStackingContext(parentStyle))
176        style->setHasAutoZIndex();
177
178    // Auto z-index becomes 0 for the root element and transparent objects. This prevents
179    // cases where objects that should be blended as a single unit end up with a non-transparent
180    // object wedged in between them. Auto z-index also becomes 0 for objects that specify transforms/masks/reflections.
181    if (style->hasAutoZIndex() && ((e && e->document().documentElement() == e)
182        || style->hasOpacity()
183        || style->hasTransformRelatedProperty()
184        || style->hasMask()
185        || style->clipPath()
186        || style->boxReflect()
187        || style->hasFilter()
188        || style->hasBlendMode()
189        || style->hasIsolation()
190        || style->position() == FixedPosition
191        || isInTopLayer(e, style)
192        || hasWillChangeThatCreatesStackingContext(style)))
193        style->setZIndex(0);
194
195    // will-change:transform should result in the same rendering behavior as having a transform,
196    // including the creation of a containing block for fixed position descendants.
197    if (!style->hasTransform() && (style->willChangeProperties().contains(CSSPropertyWebkitTransform) || style->willChangeProperties().contains(CSSPropertyTransform))) {
198        bool makeIdentity = true;
199        style->setTransform(TransformOperations(makeIdentity));
200    }
201
202    if (doesNotInheritTextDecoration(style, e))
203        style->clearAppliedTextDecorations();
204
205    style->applyTextDecorations();
206
207    if (style->overflowX() != OVISIBLE || style->overflowY() != OVISIBLE)
208        adjustOverflow(style);
209
210    // Cull out any useless layers and also repeat patterns into additional layers.
211    style->adjustBackgroundLayers();
212    style->adjustMaskLayers();
213
214    // Let the theme also have a crack at adjusting the style.
215    if (style->hasAppearance())
216        RenderTheme::theme().adjustStyle(style, e, cachedUAStyle);
217
218    // If we have first-letter pseudo style, transitions, or animations, do not share this style.
219    if (style->hasPseudoStyle(FIRST_LETTER) || style->transitions() || style->animations())
220        style->setUnique();
221
222    // FIXME: when dropping the -webkit prefix on transform-style, we should also have opacity < 1 cause flattening.
223    if (style->preserves3D() && (style->overflowX() != OVISIBLE
224        || style->overflowY() != OVISIBLE
225        || style->hasFilter()))
226        style->setTransformStyle3D(TransformStyle3DFlat);
227
228    if (e && e->isSVGElement()) {
229        // Only the root <svg> element in an SVG document fragment tree honors css position
230        if (!(isSVGSVGElement(*e) && e->parentNode() && !e->parentNode()->isSVGElement()))
231            style->setPosition(RenderStyle::initialPosition());
232
233        // SVG text layout code expects us to be a block-level style element.
234        if ((isSVGForeignObjectElement(*e) || isSVGTextElement(*e)) && style->isDisplayInlineType())
235            style->setDisplay(BLOCK);
236    }
237
238    if (e && e->renderStyle() && e->renderStyle()->textAutosizingMultiplier() != 1) {
239        // Preserve the text autosizing multiplier on style recalc.
240        // (The autosizer will update it during layout if it needs to be changed.)
241        style->setTextAutosizingMultiplier(e->renderStyle()->textAutosizingMultiplier());
242        style->setUnique();
243    }
244
245    adjustStyleForAlignment(*style, *parentStyle);
246}
247
248void StyleAdjuster::adjustStyleForAlignment(RenderStyle& style, const RenderStyle& parentStyle)
249{
250    bool isFlexOrGrid = style.isDisplayFlexibleOrGridBox();
251    bool absolutePositioned = style.position() == AbsolutePosition;
252
253    // If the inherited value of justify-items includes the legacy keyword, 'auto'
254    // computes to the the inherited value.
255    // Otherwise, auto computes to:
256    //  - 'stretch' for flex containers and grid containers.
257    //  - 'start' for everything else.
258    if (style.justifyItems() == ItemPositionAuto) {
259        if (parentStyle.justifyItemsPositionType() == LegacyPosition) {
260            style.setJustifyItems(parentStyle.justifyItems());
261            style.setJustifyItemsPositionType(parentStyle.justifyItemsPositionType());
262        } else if (isFlexOrGrid) {
263            style.setJustifyItems(ItemPositionStretch);
264        }
265    }
266
267    // The 'auto' keyword computes to 'stretch' on absolutely-positioned elements,
268    // and to the computed value of justify-items on the parent (minus
269    // any legacy keywords) on all other boxes.
270    if (style.justifySelf() == ItemPositionAuto) {
271        if (absolutePositioned) {
272            style.setJustifySelf(ItemPositionStretch);
273        } else {
274            style.setJustifySelf(parentStyle.justifyItems());
275            style.setJustifySelfOverflowAlignment(parentStyle.justifyItemsOverflowAlignment());
276        }
277    }
278
279    // The 'auto' keyword computes to:
280    //  - 'stretch' for flex containers and grid containers,
281    //  - 'start' for everything else.
282    if (style.alignItems() == ItemPositionAuto) {
283        if (isFlexOrGrid)
284            style.setAlignItems(ItemPositionStretch);
285    }
286
287    // The 'auto' keyword computes to 'stretch' on absolutely-positioned elements,
288    // and to the computed value of align-items on the parent (minus
289    // any 'legacy' keywords) on all other boxes.
290    if (style.alignSelf() == ItemPositionAuto) {
291        if (absolutePositioned) {
292            style.setAlignSelf(ItemPositionStretch);
293        } else {
294            style.setAlignSelf(parentStyle.alignItems());
295            style.setAlignSelfOverflowAlignment(parentStyle.alignItemsOverflowAlignment());
296        }
297    }
298}
299
300void StyleAdjuster::adjustStyleForTagName(RenderStyle* style, RenderStyle* parentStyle, Element& element)
301{
302    // <div> and <span> are the most common elements on the web, we skip all the work for them.
303    if (isHTMLDivElement(element) || isHTMLSpanElement(element))
304        return;
305
306    if (isHTMLTableCellElement(element)) {
307        // If we have a <td> that specifies a float property, in quirks mode we just drop the float property.
308        // FIXME: Why is this only <td> and not <th>?
309        if (element.hasTagName(tdTag) && m_useQuirksModeStyles) {
310            style->setDisplay(TABLE_CELL);
311            style->setFloating(NoFloat);
312        }
313        // FIXME: We shouldn't be overriding start/-webkit-auto like this. Do it in html.css instead.
314        // Table headers with a text-align of -webkit-auto will change the text-align to center.
315        if (element.hasTagName(thTag) && style->textAlign() == TASTART)
316            style->setTextAlign(CENTER);
317        if (style->whiteSpace() == KHTML_NOWRAP) {
318            // Figure out if we are really nowrapping or if we should just
319            // use normal instead. If the width of the cell is fixed, then
320            // we don't actually use NOWRAP.
321            if (style->width().isFixed())
322                style->setWhiteSpace(NORMAL);
323            else
324                style->setWhiteSpace(NOWRAP);
325        }
326        return;
327    }
328
329    if (isHTMLTableElement(element)) {
330        // Sites commonly use display:inline/block on <td>s and <table>s. In quirks mode we force
331        // these tags to retain their display types.
332        if (m_useQuirksModeStyles)
333            style->setDisplay(style->isDisplayInlineType() ? INLINE_TABLE : TABLE);
334        // Tables never support the -webkit-* values for text-align and will reset back to the default.
335        if (style->textAlign() == WEBKIT_LEFT || style->textAlign() == WEBKIT_CENTER || style->textAlign() == WEBKIT_RIGHT)
336            style->setTextAlign(TASTART);
337        return;
338    }
339
340    if (isHTMLFrameElement(element) || isHTMLFrameSetElement(element)) {
341        // Frames and framesets never honor position:relative or position:absolute. This is necessary to
342        // fix a crash where a site tries to position these objects. They also never honor display.
343        style->setPosition(StaticPosition);
344        style->setDisplay(BLOCK);
345        return;
346    }
347
348    if (isHTMLRTElement(element)) {
349        // Ruby text does not support float or position. This might change with evolution of the specification.
350        style->setPosition(StaticPosition);
351        style->setFloating(NoFloat);
352        return;
353    }
354
355    if (isHTMLLegendElement(element)) {
356        style->setDisplay(BLOCK);
357        return;
358    }
359
360    if (isHTMLMarqueeElement(element)) {
361        // For now, <marquee> requires an overflow clip to work properly.
362        style->setOverflowX(OHIDDEN);
363        style->setOverflowY(OHIDDEN);
364        return;
365    }
366
367    if (isHTMLTextAreaElement(element)) {
368        // Textarea considers overflow visible as auto.
369        style->setOverflowX(style->overflowX() == OVISIBLE ? OAUTO : style->overflowX());
370        style->setOverflowY(style->overflowY() == OVISIBLE ? OAUTO : style->overflowY());
371        return;
372    }
373
374    if (isHTMLPlugInElement(element)) {
375        style->setRequiresAcceleratedCompositingForExternalReasons(toHTMLPlugInElement(element).shouldAccelerate());
376
377        // Plugins should get the standard replaced width/height instead of 'auto'.
378        // Replaced renderers get this for free, and fallback content doesn't count.
379        if (toHTMLPlugInElement(element).usePlaceholderContent()) {
380            if (style->width().isAuto())
381                style->setWidth(Length(RenderReplaced::defaultWidth, Fixed));
382            if (style->height().isAuto())
383                style->setHeight(Length(RenderReplaced::defaultHeight, Fixed));
384        }
385
386        return;
387    }
388}
389
390void StyleAdjuster::adjustOverflow(RenderStyle* style)
391{
392    ASSERT(style->overflowX() != OVISIBLE || style->overflowY() != OVISIBLE);
393
394    // If either overflow value is not visible, change to auto.
395    if (style->overflowX() == OVISIBLE && style->overflowY() != OVISIBLE) {
396        // FIXME: Once we implement pagination controls, overflow-x should default to hidden
397        // if overflow-y is set to -webkit-paged-x or -webkit-page-y. For now, we'll let it
398        // default to auto so we can at least scroll through the pages.
399        style->setOverflowX(OAUTO);
400    } else if (style->overflowY() == OVISIBLE && style->overflowX() != OVISIBLE) {
401        style->setOverflowY(OAUTO);
402    }
403
404    // Table rows, sections and the table itself will support overflow:hidden and will ignore scroll/auto.
405    // FIXME: Eventually table sections will support auto and scroll.
406    if (style->display() == TABLE || style->display() == INLINE_TABLE
407        || style->display() == TABLE_ROW_GROUP || style->display() == TABLE_ROW) {
408        if (style->overflowX() != OVISIBLE && style->overflowX() != OHIDDEN)
409            style->setOverflowX(OVISIBLE);
410        if (style->overflowY() != OVISIBLE && style->overflowY() != OHIDDEN)
411            style->setOverflowY(OVISIBLE);
412    }
413
414    // Menulists should have visible overflow
415    if (style->appearance() == MenulistPart) {
416        style->setOverflowX(OVISIBLE);
417        style->setOverflowY(OVISIBLE);
418    }
419}
420
421void StyleAdjuster::adjustStyleForDisplay(RenderStyle* style, RenderStyle* parentStyle)
422{
423    if (style->display() == BLOCK && !style->isFloating())
424        return;
425
426    // FIXME: Don't support this mutation for pseudo styles like first-letter or first-line, since it's not completely
427    // clear how that should work.
428    if (style->display() == INLINE && style->styleType() == NOPSEUDO && style->writingMode() != parentStyle->writingMode())
429        style->setDisplay(INLINE_BLOCK);
430
431    // After performing the display mutation, check table rows. We do not honor position: relative table rows or cells.
432    // This has been established for position: relative in CSS2.1 (and caused a crash in containingBlock()
433    // on some sites).
434    if ((style->display() == TABLE_HEADER_GROUP || style->display() == TABLE_ROW_GROUP
435        || style->display() == TABLE_FOOTER_GROUP || style->display() == TABLE_ROW)
436        && style->position() == RelativePosition)
437        style->setPosition(StaticPosition);
438
439    // writing-mode does not apply to table row groups, table column groups, table rows, and table columns.
440    // FIXME: Table cells should be allowed to be perpendicular or flipped with respect to the table, though.
441    if (style->display() == TABLE_COLUMN || style->display() == TABLE_COLUMN_GROUP || style->display() == TABLE_FOOTER_GROUP
442        || style->display() == TABLE_HEADER_GROUP || style->display() == TABLE_ROW || style->display() == TABLE_ROW_GROUP
443        || style->display() == TABLE_CELL)
444        style->setWritingMode(parentStyle->writingMode());
445
446    // FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting
447    // of block-flow to anything other than TopToBottomWritingMode.
448    // https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support.
449    if (style->writingMode() != TopToBottomWritingMode && (style->display() == BOX || style->display() == INLINE_BOX))
450        style->setWritingMode(TopToBottomWritingMode);
451
452    if (parentStyle->isDisplayFlexibleOrGridBox()) {
453        style->setFloating(NoFloat);
454        style->setDisplay(equivalentBlockDisplay(style->display(), style->isFloating(), !m_useQuirksModeStyles));
455    }
456}
457
458}
459