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 "HTMLNames.h"
33#include "SVGNames.h"
34#include "core/dom/ContainerNode.h"
35#include "core/dom/Document.h"
36#include "core/dom/Element.h"
37#include "core/html/HTMLHtmlElement.h"
38#include "core/html/HTMLIFrameElement.h"
39#include "core/html/HTMLInputElement.h"
40#include "core/html/HTMLTableElement.h"
41#include "core/html/HTMLTextAreaElement.h"
42#include "core/page/FrameView.h"
43#include "core/page/Page.h"
44#include "core/page/Settings.h"
45#include "core/platform/Length.h"
46#include "core/rendering/Pagination.h"
47#include "core/rendering/RenderTheme.h"
48#include "core/rendering/style/GridPosition.h"
49#include "core/rendering/style/RenderStyle.h"
50#include "core/rendering/style/RenderStyleConstants.h"
51#include "wtf/Assertions.h"
52
53namespace WebCore {
54
55using namespace HTMLNames;
56
57// FIXME: This is duplicated with StyleResolver.cpp
58// Perhaps this should move onto ElementResolveContext or even Element?
59static inline bool isAtShadowBoundary(const Element* element)
60{
61    if (!element)
62        return false;
63    ContainerNode* parentNode = element->parentNode();
64    return parentNode && parentNode->isShadowRoot();
65}
66
67
68static void addIntrinsicMargins(RenderStyle* style)
69{
70    // Intrinsic margin value.
71    const int intrinsicMargin = 2 * style->effectiveZoom();
72
73    // FIXME: Using width/height alone and not also dealing with min-width/max-width is flawed.
74    // FIXME: Using "quirk" to decide the margin wasn't set is kind of lame.
75    if (style->width().isIntrinsicOrAuto()) {
76        if (style->marginLeft().quirk())
77            style->setMarginLeft(Length(intrinsicMargin, Fixed));
78        if (style->marginRight().quirk())
79            style->setMarginRight(Length(intrinsicMargin, Fixed));
80    }
81
82    if (style->height().isAuto()) {
83        if (style->marginTop().quirk())
84            style->setMarginTop(Length(intrinsicMargin, Fixed));
85        if (style->marginBottom().quirk())
86            style->setMarginBottom(Length(intrinsicMargin, Fixed));
87    }
88}
89
90static EDisplay equivalentBlockDisplay(EDisplay display, bool isFloating, bool strictParsing)
91{
92    switch (display) {
93    case BLOCK:
94    case TABLE:
95    case BOX:
96    case FLEX:
97    case GRID:
98    case LAZY_BLOCK:
99        return display;
100
101    case LIST_ITEM:
102        // It is a WinIE bug that floated list items lose their bullets, so we'll emulate the quirk, but only in quirks mode.
103        if (!strictParsing && isFloating)
104            return BLOCK;
105        return display;
106    case INLINE_TABLE:
107        return TABLE;
108    case INLINE_BOX:
109        return BOX;
110    case INLINE_FLEX:
111        return FLEX;
112    case INLINE_GRID:
113        return GRID;
114
115    case INLINE:
116    case RUN_IN:
117    case COMPACT:
118    case INLINE_BLOCK:
119    case TABLE_ROW_GROUP:
120    case TABLE_HEADER_GROUP:
121    case TABLE_FOOTER_GROUP:
122    case TABLE_ROW:
123    case TABLE_COLUMN_GROUP:
124    case TABLE_COLUMN:
125    case TABLE_CELL:
126    case TABLE_CAPTION:
127        return BLOCK;
128    case NONE:
129        ASSERT_NOT_REACHED();
130        return NONE;
131    }
132    ASSERT_NOT_REACHED();
133    return BLOCK;
134}
135
136// CSS requires text-decoration to be reset at each DOM element for tables,
137// inline blocks, inline tables, run-ins, shadow DOM crossings, floating elements,
138// and absolute or relatively positioned elements.
139static bool doesNotInheritTextDecoration(const RenderStyle* style, const Element* e)
140{
141    return style->display() == TABLE || style->display() == INLINE_TABLE || style->display() == RUN_IN
142        || style->display() == INLINE_BLOCK || style->display() == INLINE_BOX || isAtShadowBoundary(e)
143        || style->isFloating() || style->hasOutOfFlowPosition();
144}
145
146// FIXME: This helper is only needed because pseudoStyleForElement passes a null
147// element to adjustRenderStyle, so we can't just use element->isInTopLayer().
148static bool isInTopLayer(const Element* element, const RenderStyle* style)
149{
150    return (element && element->isInTopLayer()) || (style && style->styleType() == BACKDROP);
151}
152
153static bool isDisplayFlexibleBox(EDisplay display)
154{
155    return display == FLEX || display == INLINE_FLEX;
156}
157
158static bool isDisplayGridBox(EDisplay display)
159{
160    return display == GRID || display == INLINE_GRID;
161}
162
163void StyleAdjuster::adjustRenderStyle(RenderStyle* style, RenderStyle* parentStyle, Element *e)
164{
165    ASSERT(parentStyle);
166
167    // Cache our original display.
168    style->setOriginalDisplay(style->display());
169
170    if (style->display() != NONE) {
171        // If we have a <td> that specifies a float property, in quirks mode we just drop the float
172        // property.
173        // Sites also commonly use display:inline/block on <td>s and <table>s. In quirks mode we force
174        // these tags to retain their display types.
175        if (m_useQuirksModeStyles && e) {
176            if (e->hasTagName(tdTag)) {
177                style->setDisplay(TABLE_CELL);
178                style->setFloating(NoFloat);
179            } else if (isHTMLTableElement(e)) {
180                style->setDisplay(style->isDisplayInlineType() ? INLINE_TABLE : TABLE);
181            }
182        }
183
184        if (e && (e->hasTagName(tdTag) || e->hasTagName(thTag))) {
185            if (style->whiteSpace() == KHTML_NOWRAP) {
186                // Figure out if we are really nowrapping or if we should just
187                // use normal instead. If the width of the cell is fixed, then
188                // we don't actually use NOWRAP.
189                if (style->width().isFixed())
190                    style->setWhiteSpace(NORMAL);
191                else
192                    style->setWhiteSpace(NOWRAP);
193            }
194        }
195
196        // Tables never support the -webkit-* values for text-align and will reset back to the default.
197        if (e && isHTMLTableElement(e) && (style->textAlign() == WEBKIT_LEFT || style->textAlign() == WEBKIT_CENTER || style->textAlign() == WEBKIT_RIGHT))
198            style->setTextAlign(TASTART);
199
200        // Frames and framesets never honor position:relative or position:absolute. This is necessary to
201        // fix a crash where a site tries to position these objects. They also never honor display.
202        if (e && (e->hasTagName(frameTag) || e->hasTagName(framesetTag))) {
203            style->setPosition(StaticPosition);
204            style->setDisplay(BLOCK);
205        }
206
207        // Ruby text does not support float or position. This might change with evolution of the specification.
208        if (e && e->hasTagName(rtTag)) {
209            style->setPosition(StaticPosition);
210            style->setFloating(NoFloat);
211        }
212
213        // FIXME: We shouldn't be overriding start/-webkit-auto like this. Do it in html.css instead.
214        // Table headers with a text-align of -webkit-auto will change the text-align to center.
215        if (e && e->hasTagName(thTag) && style->textAlign() == TASTART)
216            style->setTextAlign(CENTER);
217
218        if (e && e->hasTagName(legendTag))
219            style->setDisplay(BLOCK);
220
221        // Per the spec, position 'static' and 'relative' in the top layer compute to 'absolute'.
222        if (isInTopLayer(e, style) && (style->position() == StaticPosition || style->position() == RelativePosition))
223            style->setPosition(AbsolutePosition);
224
225        // Absolute/fixed positioned elements, floating elements and the document element need block-like outside display.
226        if (style->hasOutOfFlowPosition() || style->isFloating() || (e && e->document()->documentElement() == e))
227            style->setDisplay(equivalentBlockDisplay(style->display(), style->isFloating(), !m_useQuirksModeStyles));
228
229        // FIXME: Don't support this mutation for pseudo styles like first-letter or first-line, since it's not completely
230        // clear how that should work.
231        if (style->display() == INLINE && style->styleType() == NOPSEUDO && style->writingMode() != parentStyle->writingMode())
232            style->setDisplay(INLINE_BLOCK);
233
234        // After performing the display mutation, check table rows. We do not honor position:relative or position:sticky on
235        // table rows or cells. This has been established for position:relative in CSS2.1 (and caused a crash in containingBlock()
236        // on some sites).
237        if ((style->display() == TABLE_HEADER_GROUP || style->display() == TABLE_ROW_GROUP
238            || style->display() == TABLE_FOOTER_GROUP || style->display() == TABLE_ROW)
239            && style->hasInFlowPosition())
240            style->setPosition(StaticPosition);
241
242        // writing-mode does not apply to table row groups, table column groups, table rows, and table columns.
243        // FIXME: Table cells should be allowed to be perpendicular or flipped with respect to the table, though.
244        if (style->display() == TABLE_COLUMN || style->display() == TABLE_COLUMN_GROUP || style->display() == TABLE_FOOTER_GROUP
245            || style->display() == TABLE_HEADER_GROUP || style->display() == TABLE_ROW || style->display() == TABLE_ROW_GROUP
246            || style->display() == TABLE_CELL)
247            style->setWritingMode(parentStyle->writingMode());
248
249        // FIXME: Since we don't support block-flow on flexible boxes yet, disallow setting
250        // of block-flow to anything other than TopToBottomWritingMode.
251        // https://bugs.webkit.org/show_bug.cgi?id=46418 - Flexible box support.
252        if (style->writingMode() != TopToBottomWritingMode && (style->display() == BOX || style->display() == INLINE_BOX))
253            style->setWritingMode(TopToBottomWritingMode);
254
255        if (isDisplayFlexibleBox(parentStyle->display()) || isDisplayGridBox(parentStyle->display())) {
256            style->setFloating(NoFloat);
257            style->setDisplay(equivalentBlockDisplay(style->display(), style->isFloating(), !m_useQuirksModeStyles));
258        }
259    }
260
261    // Make sure our z-index value is only applied if the object is positioned.
262    if (style->position() == StaticPosition && !isDisplayFlexibleBox(parentStyle->display()))
263        style->setHasAutoZIndex();
264
265    // Auto z-index becomes 0 for the root element and transparent objects. This prevents
266    // cases where objects that should be blended as a single unit end up with a non-transparent
267    // object wedged in between them. Auto z-index also becomes 0 for objects that specify transforms/masks/reflections.
268    if (style->hasAutoZIndex() && ((e && e->document()->documentElement() == e)
269        || style->opacity() < 1.0f
270        || style->hasTransformRelatedProperty()
271        || style->hasMask()
272        || style->clipPath()
273        || style->boxReflect()
274        || style->hasFilter()
275        || style->hasBlendMode()
276        || style->position() == StickyPosition
277        || (style->position() == FixedPosition && e && e->document()->page() && e->document()->page()->settings()->fixedPositionCreatesStackingContext())
278        || isInTopLayer(e, style)
279        ))
280        style->setZIndex(0);
281
282    // Textarea considers overflow visible as auto.
283    if (e && isHTMLTextAreaElement(e)) {
284        style->setOverflowX(style->overflowX() == OVISIBLE ? OAUTO : style->overflowX());
285        style->setOverflowY(style->overflowY() == OVISIBLE ? OAUTO : style->overflowY());
286    }
287
288    // For now, <marquee> requires an overflow clip to work properly.
289    if (e && e->hasTagName(marqueeTag)) {
290        style->setOverflowX(OHIDDEN);
291        style->setOverflowY(OHIDDEN);
292    }
293
294    if (doesNotInheritTextDecoration(style, e))
295        style->setTextDecorationsInEffect(style->textDecoration());
296    else
297        style->addToTextDecorationsInEffect(style->textDecoration());
298
299    // If either overflow value is not visible, change to auto.
300    if (style->overflowX() == OVISIBLE && style->overflowY() != OVISIBLE) {
301        // FIXME: Once we implement pagination controls, overflow-x should default to hidden
302        // if overflow-y is set to -webkit-paged-x or -webkit-page-y. For now, we'll let it
303        // default to auto so we can at least scroll through the pages.
304        style->setOverflowX(OAUTO);
305    } else if (style->overflowY() == OVISIBLE && style->overflowX() != OVISIBLE) {
306        style->setOverflowY(OAUTO);
307    }
308
309    // Call setStylesForPaginationMode() if a pagination mode is set for any non-root elements. If these
310    // styles are specified on a root element, then they will be incorporated in
311    // StyleAdjuster::styleForDocument().
312    if ((style->overflowY() == OPAGEDX || style->overflowY() == OPAGEDY) && !(e && (isHTMLHtmlElement(e) || e->hasTagName(bodyTag))))
313        Pagination::setStylesForPaginationMode(WebCore::paginationModeForRenderStyle(style), style);
314
315    // Table rows, sections and the table itself will support overflow:hidden and will ignore scroll/auto.
316    // FIXME: Eventually table sections will support auto and scroll.
317    if (style->display() == TABLE || style->display() == INLINE_TABLE
318        || style->display() == TABLE_ROW_GROUP || style->display() == TABLE_ROW) {
319        if (style->overflowX() != OVISIBLE && style->overflowX() != OHIDDEN)
320            style->setOverflowX(OVISIBLE);
321        if (style->overflowY() != OVISIBLE && style->overflowY() != OHIDDEN)
322            style->setOverflowY(OVISIBLE);
323    }
324
325    // Menulists should have visible overflow
326    if (style->appearance() == MenulistPart) {
327        style->setOverflowX(OVISIBLE);
328        style->setOverflowY(OVISIBLE);
329    }
330
331    // Cull out any useless layers and also repeat patterns into additional layers.
332    style->adjustBackgroundLayers();
333    style->adjustMaskLayers();
334
335    // Do the same for animations and transitions.
336    style->adjustAnimations();
337    style->adjustTransitions();
338
339    // Important: Intrinsic margins get added to controls before the theme has adjusted the style, since the theme will
340    // alter fonts and heights/widths.
341    if (e && e->isFormControlElement() && style->fontSize() >= 11) {
342        // Don't apply intrinsic margins to image buttons. The designer knows how big the images are,
343        // so we have to treat all image buttons as though they were explicitly sized.
344        if (!e->hasTagName(inputTag) || !toHTMLInputElement(e)->isImageButton())
345            addIntrinsicMargins(style);
346    }
347
348    // Let the theme also have a crack at adjusting the style.
349    if (style->hasAppearance())
350        RenderTheme::defaultTheme()->adjustStyle(style, e, m_cachedUAStyle);
351
352    // If we have first-letter pseudo style, do not share this style.
353    if (style->hasPseudoStyle(FIRST_LETTER))
354        style->setUnique();
355
356    // FIXME: when dropping the -webkit prefix on transform-style, we should also have opacity < 1 cause flattening.
357    if (style->preserves3D() && (style->overflowX() != OVISIBLE
358        || style->overflowY() != OVISIBLE
359        || style->hasFilter()))
360        style->setTransformStyle3D(TransformStyle3DFlat);
361
362    // Seamless iframes behave like blocks. Map their display to inline-block when marked inline.
363    if (e && e->hasTagName(iframeTag) && style->display() == INLINE && toHTMLIFrameElement(e)->shouldDisplaySeamlessly())
364        style->setDisplay(INLINE_BLOCK);
365
366    adjustGridItemPosition(style, parentStyle);
367
368    if (e && e->isSVGElement()) {
369        // Spec: http://www.w3.org/TR/SVG/masking.html#OverflowProperty
370        if (style->overflowY() == OSCROLL)
371            style->setOverflowY(OHIDDEN);
372        else if (style->overflowY() == OAUTO)
373            style->setOverflowY(OVISIBLE);
374
375        if (style->overflowX() == OSCROLL)
376            style->setOverflowX(OHIDDEN);
377        else if (style->overflowX() == OAUTO)
378            style->setOverflowX(OVISIBLE);
379
380        // Only the root <svg> element in an SVG document fragment tree honors css position
381        if (!(e->hasTagName(SVGNames::svgTag) && e->parentNode() && !e->parentNode()->isSVGElement()))
382            style->setPosition(RenderStyle::initialPosition());
383
384        // RenderSVGRoot handles zooming for the whole SVG subtree, so foreignObject content should
385        // not be scaled again.
386        if (e->hasTagName(SVGNames::foreignObjectTag))
387            style->setEffectiveZoom(RenderStyle::initialZoom());
388    }
389}
390
391void StyleAdjuster::adjustGridItemPosition(RenderStyle* style, RenderStyle* parentStyle) const
392{
393    const GridPosition& columnStartPosition = style->gridColumnStart();
394    const GridPosition& columnEndPosition = style->gridColumnEnd();
395    const GridPosition& rowStartPosition = style->gridRowStart();
396    const GridPosition& rowEndPosition = style->gridRowEnd();
397
398    // If opposing grid-placement properties both specify a grid span, they both compute to ‘auto’.
399    if (columnStartPosition.isSpan() && columnEndPosition.isSpan()) {
400        style->setGridColumnStart(GridPosition());
401        style->setGridColumnEnd(GridPosition());
402    }
403
404    if (rowStartPosition.isSpan() && rowEndPosition.isSpan()) {
405        style->setGridRowStart(GridPosition());
406        style->setGridRowEnd(GridPosition());
407    }
408
409    // Unknown named grid area compute to 'auto'.
410    const NamedGridAreaMap& map = parentStyle->namedGridArea();
411
412#define CLEAR_UNKNOWN_NAMED_AREA(prop, Prop) \
413    if (prop.isNamedGridArea() && !map.contains(prop.namedGridLine())) \
414        style->setGrid##Prop(GridPosition());
415
416    CLEAR_UNKNOWN_NAMED_AREA(columnStartPosition, ColumnStart);
417    CLEAR_UNKNOWN_NAMED_AREA(columnEndPosition, ColumnEnd);
418    CLEAR_UNKNOWN_NAMED_AREA(rowStartPosition, RowStart);
419    CLEAR_UNKNOWN_NAMED_AREA(rowEndPosition, RowEnd);
420}
421
422}
423