1/*
2 * Copyright (C) 2007, 2013 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "RenderLayoutState.h"
28
29#include "RenderFragmentedFlow.h"
30#include "RenderInline.h"
31#include "RenderLayer.h"
32#include "RenderMultiColumnFlow.h"
33#include "RenderView.h"
34#include <wtf/WeakPtr.h>
35
36namespace WebCore {
37
38RenderLayoutState::RenderLayoutState(RenderElement& renderer, IsPaginated isPaginated)
39 : m_clipped(false)
40 , m_isPaginated(isPaginated == IsPaginated::Yes)
41 , m_pageLogicalHeightChanged(false)
42#if !ASSERT_DISABLED
43 , m_layoutDeltaXSaturated(false)
44 , m_layoutDeltaYSaturated(false)
45#endif
46#ifndef NDEBUG
47 , m_renderer(&renderer)
48#endif
49{
50 if (RenderElement* container = renderer.container()) {
51 FloatPoint absContentPoint = container->localToAbsolute(FloatPoint(), UseTransforms);
52 m_paintOffset = LayoutSize(absContentPoint.x(), absContentPoint.y());
53
54 if (container->hasOverflowClip()) {
55 m_clipped = true;
56 auto& containerBox = downcast<RenderBox>(*container);
57 m_clipRect = LayoutRect(toLayoutPoint(m_paintOffset), containerBox.cachedSizeForOverflowClip());
58 m_paintOffset -= toLayoutSize(containerBox.scrollPosition());
59 }
60 }
61 if (m_isPaginated) {
62 // This is just a flag for known page height (see RenderBlockFlow::checkForPaginationLogicalHeightChange).
63 m_pageLogicalHeight = 1;
64 }
65}
66
67RenderLayoutState::RenderLayoutState(const FrameViewLayoutContext::LayoutStateStack& layoutStateStack, RenderBox& renderer, const LayoutSize& offset, LayoutUnit pageLogicalHeight, bool pageLogicalHeightChanged)
68 : m_clipped(false)
69 , m_isPaginated(false)
70 , m_pageLogicalHeightChanged(false)
71#if !ASSERT_DISABLED
72 , m_layoutDeltaXSaturated(false)
73 , m_layoutDeltaYSaturated(false)
74#endif
75#ifndef NDEBUG
76 , m_renderer(&renderer)
77#endif
78{
79 if (!layoutStateStack.isEmpty()) {
80 auto& ancestor = *layoutStateStack.last().get();
81 computeOffsets(ancestor, renderer, offset);
82 computeClipRect(ancestor, renderer);
83 }
84 computePaginationInformation(layoutStateStack, renderer, pageLogicalHeight, pageLogicalHeightChanged);
85}
86
87void RenderLayoutState::computeOffsets(const RenderLayoutState& ancestor, RenderBox& renderer, LayoutSize offset)
88{
89 bool fixed = renderer.isFixedPositioned();
90 if (fixed) {
91 FloatPoint fixedOffset = renderer.view().localToAbsolute(FloatPoint(), IsFixed);
92 m_paintOffset = LayoutSize(fixedOffset.x(), fixedOffset.y()) + offset;
93 } else
94 m_paintOffset = ancestor.paintOffset() + offset;
95
96 if (renderer.isOutOfFlowPositioned() && !fixed) {
97 if (auto* container = renderer.container()) {
98 if (container->isInFlowPositioned() && is<RenderInline>(*container))
99 m_paintOffset += downcast<RenderInline>(*container).offsetForInFlowPositionedInline(&renderer);
100 }
101 }
102
103 m_layoutOffset = m_paintOffset;
104
105 if (renderer.isInFlowPositioned() && renderer.hasLayer())
106 m_paintOffset += renderer.layer()->offsetForInFlowPosition();
107
108 if (renderer.hasOverflowClip())
109 m_paintOffset -= toLayoutSize(renderer.scrollPosition());
110
111 m_layoutDelta = ancestor.layoutDelta();
112#if !ASSERT_DISABLED
113 m_layoutDeltaXSaturated = ancestor.m_layoutDeltaXSaturated;
114 m_layoutDeltaYSaturated = ancestor.m_layoutDeltaYSaturated;
115#endif
116}
117
118void RenderLayoutState::computeClipRect(const RenderLayoutState& ancestor, RenderBox& renderer)
119{
120 m_clipped = !renderer.isFixedPositioned() && ancestor.isClipped();
121 if (m_clipped)
122 m_clipRect = ancestor.clipRect();
123 if (!renderer.hasOverflowClip())
124 return;
125
126 auto paintOffsetForClipRect = toLayoutPoint(m_paintOffset + toLayoutSize(renderer.scrollPosition()));
127 LayoutRect clipRect(paintOffsetForClipRect + renderer.view().frameView().layoutContext().layoutDelta(), renderer.cachedSizeForOverflowClip());
128 if (m_clipped)
129 m_clipRect.intersect(clipRect);
130 else
131 m_clipRect = clipRect;
132 m_clipped = true;
133 // FIXME: <http://bugs.webkit.org/show_bug.cgi?id=13443> Apply control clip if present.
134}
135
136void RenderLayoutState::computePaginationInformation(const FrameViewLayoutContext::LayoutStateStack& layoutStateStack, RenderBox& renderer, LayoutUnit pageLogicalHeight, bool pageLogicalHeightChanged)
137{
138 auto* ancestor = layoutStateStack.isEmpty() ? nullptr : layoutStateStack.last().get();
139 // If we establish a new page height, then cache the offset to the top of the first page.
140 // We can compare this later on to figure out what part of the page we're actually on.
141 if (pageLogicalHeight || renderer.isRenderFragmentedFlow()) {
142 m_pageLogicalHeight = pageLogicalHeight;
143 bool isFlipped = renderer.style().isFlippedBlocksWritingMode();
144 m_pageOffset = LayoutSize(m_layoutOffset.width() + (!isFlipped ? renderer.borderLeft() + renderer.paddingLeft() : renderer.borderRight() + renderer.paddingRight()), m_layoutOffset.height() + (!isFlipped ? renderer.borderTop() + renderer.paddingTop() : renderer.borderBottom() + renderer.paddingBottom()));
145 m_pageLogicalHeightChanged = pageLogicalHeightChanged;
146 m_isPaginated = true;
147 } else if (ancestor) {
148 // If we don't establish a new page height, then propagate the old page height and offset down.
149 m_pageLogicalHeight = ancestor->pageLogicalHeight();
150 m_pageLogicalHeightChanged = ancestor->pageLogicalHeightChanged();
151 m_pageOffset = ancestor->pageOffset();
152
153 // Disable pagination for objects we don't support. For now this includes overflow:scroll/auto, inline blocks and writing mode roots.
154 if (renderer.isUnsplittableForPagination()) {
155 m_pageLogicalHeight = 0;
156 m_isPaginated = false;
157 } else
158 m_isPaginated = m_pageLogicalHeight || renderer.enclosingFragmentedFlow();
159 }
160
161 // Propagate line grid information.
162 if (ancestor)
163 propagateLineGridInfo(*ancestor, renderer);
164
165 if (lineGrid() && (lineGrid()->style().writingMode() == renderer.style().writingMode()) && is<RenderMultiColumnFlow>(renderer))
166 computeLineGridPaginationOrigin(downcast<RenderMultiColumnFlow>(renderer));
167
168 // If we have a new grid to track, then add it to our set.
169 if (renderer.style().lineGrid() != RenderStyle::initialLineGrid() && is<RenderBlockFlow>(renderer))
170 establishLineGrid(layoutStateStack, downcast<RenderBlockFlow>(renderer));
171}
172
173LayoutUnit RenderLayoutState::pageLogicalOffset(RenderBox* child, LayoutUnit childLogicalOffset) const
174{
175 if (child->isHorizontalWritingMode())
176 return m_layoutOffset.height() + childLogicalOffset - m_pageOffset.height();
177 return m_layoutOffset.width() + childLogicalOffset - m_pageOffset.width();
178}
179
180void RenderLayoutState::computeLineGridPaginationOrigin(const RenderMultiColumnFlow& multicol)
181{
182 if (!isPaginated() || !pageLogicalHeight())
183 return;
184
185 if (!multicol.progressionIsInline())
186 return;
187 // We need to cache a line grid pagination origin so that we understand how to reset the line grid
188 // at the top of each column.
189 // Get the current line grid and offset.
190 ASSERT(m_lineGrid);
191 // Get the hypothetical line box used to establish the grid.
192 auto* lineGridBox = m_lineGrid->lineGridBox();
193 if (!lineGridBox)
194 return;
195
196 // Now determine our position on the grid. Our baseline needs to be adjusted to the nearest baseline multiple
197 // as established by the line box.
198 // FIXME: Need to handle crazy line-box-contain values that cause the root line box to not be considered. I assume
199 // the grid should honor line-box-contain.
200 LayoutUnit gridLineHeight = lineGridBox->lineBottomWithLeading() - lineGridBox->lineTopWithLeading();
201 if (!gridLineHeight)
202 return;
203
204 bool isHorizontalWritingMode = m_lineGrid->isHorizontalWritingMode();
205 LayoutUnit lineGridBlockOffset = isHorizontalWritingMode ? m_lineGridOffset.height() : m_lineGridOffset.width();
206 LayoutUnit firstLineTopWithLeading = lineGridBlockOffset + lineGridBox->lineTopWithLeading();
207 LayoutUnit pageLogicalTop = isHorizontalWritingMode ? m_pageOffset.height() : m_pageOffset.width();
208 if (pageLogicalTop <= firstLineTopWithLeading)
209 return;
210
211 // Shift to the next highest line grid multiple past the page logical top. Cache the delta
212 // between this new value and the page logical top as the pagination origin.
213 LayoutUnit remainder = roundToInt(pageLogicalTop - firstLineTopWithLeading) % roundToInt(gridLineHeight);
214 LayoutUnit paginationDelta = gridLineHeight - remainder;
215 if (isHorizontalWritingMode)
216 m_lineGridPaginationOrigin.setHeight(paginationDelta);
217 else
218 m_lineGridPaginationOrigin.setWidth(paginationDelta);
219}
220
221void RenderLayoutState::propagateLineGridInfo(const RenderLayoutState& ancestor, RenderBox& renderer)
222{
223 // Disable line grids for objects we don't support. For now this includes overflow:scroll/auto, inline blocks and
224 // writing mode roots.
225 if (renderer.isUnsplittableForPagination())
226 return;
227
228 m_lineGrid = makeWeakPtr(ancestor.lineGrid());
229 m_lineGridOffset = ancestor.lineGridOffset();
230 m_lineGridPaginationOrigin = ancestor.lineGridPaginationOrigin();
231}
232
233void RenderLayoutState::establishLineGrid(const FrameViewLayoutContext::LayoutStateStack& layoutStateStack, RenderBlockFlow& renderer)
234{
235 // First check to see if this grid has been established already.
236 if (m_lineGrid) {
237 if (m_lineGrid->style().lineGrid() == renderer.style().lineGrid())
238 return;
239 auto* currentGrid = m_lineGrid.get();
240 for (int i = layoutStateStack.size() - 1; i <= 0; --i) {
241 auto& currentState = *layoutStateStack[i].get();
242 if (currentState.m_lineGrid == currentGrid)
243 continue;
244 currentGrid = currentState.lineGrid();
245 if (!currentGrid)
246 break;
247 if (currentGrid->style().lineGrid() == renderer.style().lineGrid()) {
248 m_lineGrid = makeWeakPtr(currentGrid);
249 m_lineGridOffset = currentState.m_lineGridOffset;
250 return;
251 }
252 }
253 }
254
255 // We didn't find an already-established grid with this identifier. Our render object establishes the grid.
256 m_lineGrid = makeWeakPtr(renderer);
257 m_lineGridOffset = m_layoutOffset;
258}
259
260void RenderLayoutState::addLayoutDelta(LayoutSize delta)
261{
262 m_layoutDelta += delta;
263#if !ASSERT_DISABLED
264 m_layoutDeltaXSaturated |= m_layoutDelta.width() == LayoutUnit::max() || m_layoutDelta.width() == LayoutUnit::min();
265 m_layoutDeltaYSaturated |= m_layoutDelta.height() == LayoutUnit::max() || m_layoutDelta.height() == LayoutUnit::min();
266#endif
267}
268
269#if !ASSERT_DISABLED
270bool RenderLayoutState::layoutDeltaMatches(LayoutSize delta) const
271{
272 return (delta.width() == m_layoutDelta.width() || m_layoutDeltaXSaturated) && (delta.height() == m_layoutDelta.height() || m_layoutDeltaYSaturated);
273}
274#endif
275
276LayoutStateMaintainer::LayoutStateMaintainer(RenderBox& root, LayoutSize offset, bool disablePaintOffsetCache, LayoutUnit pageHeight, bool pageHeightChanged)
277 : m_context(root.view().frameView().layoutContext())
278 , m_paintOffsetCacheIsDisabled(disablePaintOffsetCache)
279{
280 m_didPushLayoutState = m_context.pushLayoutState(root, offset, pageHeight, pageHeightChanged);
281 if (m_didPushLayoutState && m_paintOffsetCacheIsDisabled)
282 m_context.disablePaintOffsetCache();
283}
284
285LayoutStateMaintainer::~LayoutStateMaintainer()
286{
287 if (!m_didPushLayoutState)
288 return;
289 m_context.popLayoutState();
290 if (m_paintOffsetCacheIsDisabled)
291 m_context.enablePaintOffsetCache();
292}
293
294LayoutStateDisabler::LayoutStateDisabler(FrameViewLayoutContext& context)
295 : m_context(context)
296{
297 m_context.disablePaintOffsetCache();
298}
299
300LayoutStateDisabler::~LayoutStateDisabler()
301{
302 m_context.enablePaintOffsetCache();
303}
304
305static bool shouldDisablePaintOffsetCacheForSubtree(RenderElement& subtreeLayoutRoot)
306{
307 for (auto* renderer = &subtreeLayoutRoot; renderer; renderer = renderer->container()) {
308 if (renderer->hasTransform() || renderer->hasReflection())
309 return true;
310 }
311 return false;
312}
313
314SubtreeLayoutStateMaintainer::SubtreeLayoutStateMaintainer(RenderElement* subtreeLayoutRoot)
315{
316 if (subtreeLayoutRoot) {
317 m_context = &subtreeLayoutRoot->view().frameView().layoutContext();
318 m_context->pushLayoutState(*subtreeLayoutRoot);
319 if (shouldDisablePaintOffsetCacheForSubtree(*subtreeLayoutRoot)) {
320 m_context->disablePaintOffsetCache();
321 m_didDisablePaintOffsetCache = true;
322 }
323 }
324}
325
326SubtreeLayoutStateMaintainer::~SubtreeLayoutStateMaintainer()
327{
328 if (m_context) {
329 m_context->popLayoutState();
330 if (m_didDisablePaintOffsetCache)
331 m_context->enablePaintOffsetCache();
332 }
333}
334
335PaginatedLayoutStateMaintainer::PaginatedLayoutStateMaintainer(RenderBlockFlow& flow)
336 : m_context(flow.view().frameView().layoutContext())
337 , m_pushed(m_context.pushLayoutStateForPaginationIfNeeded(flow))
338{
339}
340
341PaginatedLayoutStateMaintainer::~PaginatedLayoutStateMaintainer()
342{
343 if (m_pushed)
344 m_context.popLayoutState();
345}
346
347} // namespace WebCore
348
349