1/*
2 * Copyright (C) 2012 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 "RenderGeometryMap.h"
28
29#include "RenderFlowThread.h"
30#include "RenderLayer.h"
31#include "RenderView.h"
32#include "TransformState.h"
33#include <wtf/TemporaryChange.h>
34
35namespace WebCore {
36
37RenderGeometryMap::RenderGeometryMap(MapCoordinatesFlags flags)
38    : m_insertionPosition(notFound)
39    , m_nonUniformStepsCount(0)
40    , m_transformedStepsCount(0)
41    , m_fixedStepsCount(0)
42    , m_mapCoordinatesFlags(flags)
43{
44}
45
46RenderGeometryMap::~RenderGeometryMap()
47{
48}
49
50void RenderGeometryMap::mapToContainer(TransformState& transformState, const RenderLayerModelObject* container) const
51{
52    // If the mapping includes something like columns, we have to go via renderers.
53    if (hasNonUniformStep()) {
54        m_mapping.last().m_renderer->mapLocalToContainer(container, transformState, ApplyContainerFlip | m_mapCoordinatesFlags);
55        transformState.flatten();
56        return;
57    }
58
59    bool inFixed = false;
60#if !ASSERT_DISABLED
61    bool foundContainer = !container || (m_mapping.size() && m_mapping[0].m_renderer == container);
62#endif
63
64    for (int i = m_mapping.size() - 1; i >= 0; --i) {
65        const RenderGeometryMapStep& currentStep = m_mapping[i];
66
67        // If container is the RenderView (step 0) we want to apply its scroll offset.
68        if (i > 0 && currentStep.m_renderer == container) {
69#if !ASSERT_DISABLED
70            foundContainer = true;
71#endif
72            break;
73        }
74
75        // If this box has a transform, it acts as a fixed position container
76        // for fixed descendants, which prevents the propagation of 'fixed'
77        // unless the layer itself is also fixed position.
78        if (i && currentStep.m_hasTransform && !currentStep.m_isFixedPosition)
79            inFixed = false;
80        else if (currentStep.m_isFixedPosition)
81            inFixed = true;
82
83        if (!i) {
84            // A null container indicates mapping through the RenderView, so including its transform (the page scale).
85            if (!container && currentStep.m_transform)
86                transformState.applyTransform(*currentStep.m_transform.get());
87
88            // The root gets special treatment for fixed position
89            if (inFixed)
90                transformState.move(currentStep.m_offset.width(), currentStep.m_offset.height());
91        } else {
92            TransformState::TransformAccumulation accumulate = currentStep.m_accumulatingTransform ? TransformState::AccumulateTransform : TransformState::FlattenTransform;
93            if (currentStep.m_transform)
94                transformState.applyTransform(*currentStep.m_transform.get(), accumulate);
95            else
96                transformState.move(currentStep.m_offset.width(), currentStep.m_offset.height(), accumulate);
97        }
98    }
99
100    ASSERT(foundContainer);
101    transformState.flatten();
102}
103
104FloatPoint RenderGeometryMap::mapToContainer(const FloatPoint& p, const RenderLayerModelObject* container) const
105{
106    FloatPoint result;
107
108    if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container || (m_mapping.size() && container == m_mapping[0].m_renderer)))
109        result = p + roundedIntSize(m_accumulatedOffset);
110    else {
111        TransformState transformState(TransformState::ApplyTransformDirection, p);
112        mapToContainer(transformState, container);
113        result = transformState.lastPlanarPoint();
114    }
115
116#if !ASSERT_DISABLED
117    FloatPoint rendererMappedResult = m_mapping.last().m_renderer->localToAbsolute(p, m_mapCoordinatesFlags);
118    ASSERT(roundedIntPoint(rendererMappedResult) == roundedIntPoint(result));
119#endif
120
121    return result;
122}
123
124FloatQuad RenderGeometryMap::mapToContainer(const FloatRect& rect, const RenderLayerModelObject* container) const
125{
126    FloatRect result;
127
128    if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container || (m_mapping.size() && container == m_mapping[0].m_renderer))) {
129        result = rect;
130        result.move(m_accumulatedOffset);
131    } else {
132        TransformState transformState(TransformState::ApplyTransformDirection, rect.center(), rect);
133        mapToContainer(transformState, container);
134        result = transformState.lastPlanarQuad().boundingBox();
135    }
136
137#if !ASSERT_DISABLED
138    FloatRect rendererMappedResult = m_mapping.last().m_renderer->localToContainerQuad(rect, container, m_mapCoordinatesFlags).boundingBox();
139    // Inspector creates renderers with negative width <https://bugs.webkit.org/show_bug.cgi?id=87194>.
140    // Taking FloatQuad bounds avoids spurious assertions because of that.
141    ASSERT(enclosingIntRect(rendererMappedResult) == enclosingIntRect(FloatQuad(result).boundingBox()));
142#endif
143
144    return result;
145}
146
147void RenderGeometryMap::pushMappingsToAncestor(const RenderObject* renderer, const RenderLayerModelObject* ancestorRenderer)
148{
149    // We need to push mappings in reverse order here, so do insertions rather than appends.
150    TemporaryChange<size_t> positionChange(m_insertionPosition, m_mapping.size());
151    do {
152        renderer = renderer->pushMappingToContainer(ancestorRenderer, *this);
153    } while (renderer && renderer != ancestorRenderer);
154
155    ASSERT(m_mapping.isEmpty() || m_mapping[0].m_renderer->isRenderView());
156}
157
158static bool canMapBetweenRenderers(const RenderObject& renderer, const RenderObject& ancestor)
159{
160    for (const RenderObject* current = &renderer; ; current = current->parent()) {
161        const RenderStyle& style = current->style();
162        if (style.position() == FixedPosition || style.isFlippedBlocksWritingMode())
163            return false;
164
165        if (current->hasTransform() || current->isRenderFlowThread())
166            return false;
167
168        if (current->isSVGRoot())
169            return false;
170
171        if (current == &ancestor)
172            break;
173    }
174
175    return true;
176}
177
178void RenderGeometryMap::pushMappingsToAncestor(const RenderLayer* layer, const RenderLayer* ancestorLayer)
179{
180    const RenderObject& renderer = layer->renderer();
181
182    // We have to visit all the renderers to detect flipped blocks. This might defeat the gains
183    // from mapping via layers.
184    bool canConvertInLayerTree = ancestorLayer ? canMapBetweenRenderers(layer->renderer(), ancestorLayer->renderer()) : false;
185
186    if (canConvertInLayerTree) {
187        LayoutSize layerOffset = layer->offsetFromAncestor(ancestorLayer);
188
189        // The RenderView must be pushed first.
190        if (!m_mapping.size()) {
191            ASSERT(ancestorLayer->renderer().isRenderView());
192            pushMappingsToAncestor(&ancestorLayer->renderer(), 0);
193        }
194
195        TemporaryChange<size_t> positionChange(m_insertionPosition, m_mapping.size());
196        push(&renderer, layerOffset, /*accumulatingTransform*/ true, /*isNonUniform*/ false, /*isFixedPosition*/ false, /*hasTransform*/ false);
197        return;
198    }
199    const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? &ancestorLayer->renderer() : 0;
200    pushMappingsToAncestor(&renderer, ancestorRenderer);
201}
202
203void RenderGeometryMap::push(const RenderObject* renderer, const LayoutSize& offsetFromContainer, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform)
204{
205    ASSERT(m_insertionPosition != notFound);
206
207    m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform));
208
209    RenderGeometryMapStep& step = m_mapping[m_insertionPosition];
210    step.m_offset = offsetFromContainer;
211
212    stepInserted(step);
213}
214
215void RenderGeometryMap::push(const RenderObject* renderer, const TransformationMatrix& t, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform)
216{
217    ASSERT(m_insertionPosition != notFound);
218
219    m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform));
220
221    RenderGeometryMapStep& step = m_mapping[m_insertionPosition];
222    if (!t.isIntegerTranslation())
223        step.m_transform = std::make_unique<TransformationMatrix>(t);
224    else
225        step.m_offset = LayoutSize(t.e(), t.f());
226
227    stepInserted(step);
228}
229
230void RenderGeometryMap::pushView(const RenderView* view, const LayoutSize& scrollOffset, const TransformationMatrix* t)
231{
232    ASSERT(m_insertionPosition != notFound);
233    ASSERT(!m_insertionPosition); // The view should always be the first step.
234
235    m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(view, false, false, false, t));
236
237    RenderGeometryMapStep& step = m_mapping[m_insertionPosition];
238    step.m_offset = scrollOffset;
239    if (t)
240        step.m_transform = std::make_unique<TransformationMatrix>(*t);
241
242    stepInserted(step);
243}
244
245void RenderGeometryMap::pushRenderFlowThread(const RenderFlowThread* flowThread)
246{
247    m_mapping.append(RenderGeometryMapStep(flowThread, false, false, false, false));
248    stepInserted(m_mapping.last());
249}
250
251void RenderGeometryMap::popMappingsToAncestor(const RenderLayerModelObject* ancestorRenderer)
252{
253    ASSERT(m_mapping.size());
254
255    while (m_mapping.size() && m_mapping.last().m_renderer != ancestorRenderer) {
256        stepRemoved(m_mapping.last());
257        m_mapping.removeLast();
258    }
259}
260
261void RenderGeometryMap::popMappingsToAncestor(const RenderLayer* ancestorLayer)
262{
263    const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? &ancestorLayer->renderer() : 0;
264    popMappingsToAncestor(ancestorRenderer);
265}
266
267void RenderGeometryMap::stepInserted(const RenderGeometryMapStep& step)
268{
269    // RenderView's offset, is only applied when we have fixed-positions.
270    if (!step.m_renderer->isRenderView())
271        m_accumulatedOffset += step.m_offset;
272
273    if (step.m_isNonUniform)
274        ++m_nonUniformStepsCount;
275
276    if (step.m_transform)
277        ++m_transformedStepsCount;
278
279    if (step.m_isFixedPosition)
280        ++m_fixedStepsCount;
281}
282
283void RenderGeometryMap::stepRemoved(const RenderGeometryMapStep& step)
284{
285    // RenderView's offset, is only applied when we have fixed-positions.
286    if (!step.m_renderer->isRenderView())
287        m_accumulatedOffset -= step.m_offset;
288
289    if (step.m_isNonUniform) {
290        ASSERT(m_nonUniformStepsCount);
291        --m_nonUniformStepsCount;
292    }
293
294    if (step.m_transform) {
295        ASSERT(m_transformedStepsCount);
296        --m_transformedStepsCount;
297    }
298
299    if (step.m_isFixedPosition) {
300        ASSERT(m_fixedStepsCount);
301        --m_fixedStepsCount;
302    }
303}
304
305} // namespace WebCore
306