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