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 "core/rendering/RenderGeometryMap.h"
28
29#include "core/frame/LocalFrame.h"
30#include "core/rendering/RenderLayer.h"
31#include "core/rendering/RenderView.h"
32#include "platform/geometry/TransformState.h"
33#include "wtf/TemporaryChange.h"
34
35namespace blink {
36
37RenderGeometryMap::RenderGeometryMap(MapCoordinatesFlags flags)
38    : m_insertionPosition(kNotFound)
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 ENABLE(ASSERT)
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 root RenderView (step 0) we want to apply its fixed position offset.
68        if (i > 0 && currentStep.m_renderer == container) {
69#if ENABLE(ASSERT)
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        ASSERT(!i == isTopmostRenderView(currentStep.m_renderer));
84
85        if (!i) {
86            // A null container indicates mapping through the root RenderView, so including its transform (the page scale).
87            if (!container && currentStep.m_transform)
88                transformState.applyTransform(*currentStep.m_transform.get());
89        } else {
90            TransformState::TransformAccumulation accumulate = currentStep.m_accumulatingTransform ? TransformState::AccumulateTransform : TransformState::FlattenTransform;
91            if (currentStep.m_transform)
92                transformState.applyTransform(*currentStep.m_transform.get(), accumulate);
93            else
94                transformState.move(currentStep.m_offset.width(), currentStep.m_offset.height(), accumulate);
95        }
96
97        if (inFixed && !currentStep.m_offsetForFixedPosition.isZero()) {
98            ASSERT(currentStep.m_renderer->isRenderView());
99            transformState.move(currentStep.m_offsetForFixedPosition);
100        }
101    }
102
103    ASSERT(foundContainer);
104    transformState.flatten();
105}
106
107FloatPoint RenderGeometryMap::mapToContainer(const FloatPoint& p, const RenderLayerModelObject* container) const
108{
109    FloatPoint result;
110
111    if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container || (m_mapping.size() && container == m_mapping[0].m_renderer)))
112        result = p + m_accumulatedOffset;
113    else {
114        TransformState transformState(TransformState::ApplyTransformDirection, p);
115        mapToContainer(transformState, container);
116        result = transformState.lastPlanarPoint();
117    }
118
119#if ENABLE(ASSERT)
120    if (m_mapping.size() > 0) {
121        const RenderObject* lastRenderer = m_mapping.last().m_renderer;
122        const RenderLayer* layer = lastRenderer->enclosingLayer();
123
124        // Bounds for invisible layers are intentionally not calculated, and are
125        // therefore not necessarily expected to be correct here. This is ok,
126        // because they will be recomputed if the layer becomes visible.
127        if (!layer || !layer->subtreeIsInvisible()) {
128            FloatPoint rendererMappedResult = lastRenderer->localToContainerPoint(p, container, m_mapCoordinatesFlags);
129
130            ASSERT(roundedIntPoint(rendererMappedResult) == roundedIntPoint(result));
131        }
132    }
133#endif
134
135    return result;
136}
137
138#ifndef NDEBUG
139// Handy function to call from gdb while debugging mismatched point/rect errors.
140void RenderGeometryMap::dumpSteps() const
141{
142    fprintf(stderr, "RenderGeometryMap::dumpSteps accumulatedOffset=%d,%d\n", m_accumulatedOffset.width().toInt(), m_accumulatedOffset.height().toInt());
143    for (int i = m_mapping.size() - 1; i >= 0; --i) {
144        fprintf(stderr, " [%d] %s: offset=%d,%d", i, m_mapping[i].m_renderer->debugName().ascii().data(), m_mapping[i].m_offset.width().toInt(), m_mapping[i].m_offset.height().toInt());
145        if (m_mapping[i].m_hasTransform)
146            fprintf(stderr, " hasTransform");
147        fprintf(stderr, "\n");
148    }
149}
150#endif
151
152FloatQuad RenderGeometryMap::mapToContainer(const FloatRect& rect, const RenderLayerModelObject* container) const
153{
154    FloatRect result;
155
156    if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container || (m_mapping.size() && container == m_mapping[0].m_renderer))) {
157        result = rect;
158        result.move(m_accumulatedOffset);
159    } else {
160        TransformState transformState(TransformState::ApplyTransformDirection, rect.center(), rect);
161        mapToContainer(transformState, container);
162        result = transformState.lastPlanarQuad().boundingBox();
163    }
164
165#if ENABLE(ASSERT)
166    if (m_mapping.size() > 0) {
167        const RenderObject* lastRenderer = m_mapping.last().m_renderer;
168        const RenderLayer* layer = lastRenderer->enclosingLayer();
169
170        // Bounds for invisible layers are intentionally not calculated, and are
171        // therefore not necessarily expected to be correct here. This is ok,
172        // because they will be recomputed if the layer becomes visible.
173        if (!layer->subtreeIsInvisible() && lastRenderer->style()->visibility() == VISIBLE) {
174            FloatRect rendererMappedResult = lastRenderer->localToContainerQuad(rect, container, m_mapCoordinatesFlags).boundingBox();
175
176            // Inspector creates renderers with negative width <https://bugs.webkit.org/show_bug.cgi?id=87194>.
177            // Taking FloatQuad bounds avoids spurious assertions because of that.
178            ASSERT(enclosingIntRect(rendererMappedResult) == enclosingIntRect(FloatQuad(result).boundingBox()));
179        }
180    }
181#endif
182
183    return result;
184}
185
186void RenderGeometryMap::pushMappingsToAncestor(const RenderObject* renderer, const RenderLayerModelObject* ancestorRenderer)
187{
188    // We need to push mappings in reverse order here, so do insertions rather than appends.
189    TemporaryChange<size_t> positionChange(m_insertionPosition, m_mapping.size());
190    do {
191        renderer = renderer->pushMappingToContainer(ancestorRenderer, *this);
192    } while (renderer && renderer != ancestorRenderer);
193
194    ASSERT(m_mapping.isEmpty() || isTopmostRenderView(m_mapping[0].m_renderer));
195}
196
197static bool canMapBetweenRenderers(const RenderObject* renderer, const RenderObject* ancestor)
198{
199    for (const RenderObject* current = renderer; ; current = current->parent()) {
200        const RenderStyle* style = current->style();
201        if (style->position() == FixedPosition || style->isFlippedBlocksWritingMode())
202            return false;
203
204        if (current->hasColumns() || current->hasTransform() || current->isRenderFlowThread() || current->isSVGRoot())
205            return false;
206
207        if (current == ancestor)
208            break;
209    }
210
211    return true;
212}
213
214void RenderGeometryMap::pushMappingsToAncestor(const RenderLayer* layer, const RenderLayer* ancestorLayer)
215{
216    const RenderObject* renderer = layer->renderer();
217
218    bool crossDocument = ancestorLayer && layer->renderer()->frame() != ancestorLayer->renderer()->frame();
219    ASSERT(!crossDocument || m_mapCoordinatesFlags & TraverseDocumentBoundaries);
220
221    // We have to visit all the renderers to detect flipped blocks. This might defeat the gains
222    // from mapping via layers.
223    bool canConvertInLayerTree = (ancestorLayer && !crossDocument) ? canMapBetweenRenderers(layer->renderer(), ancestorLayer->renderer()) : false;
224
225//    fprintf(stderr, "RenderGeometryMap::pushMappingsToAncestor from layer %p to layer %p, canConvertInLayerTree=%d\n", layer, ancestorLayer, canConvertInLayerTree);
226
227    if (canConvertInLayerTree) {
228        LayoutPoint layerOffset;
229        layer->convertToLayerCoords(ancestorLayer, layerOffset);
230
231        // The RenderView must be pushed first.
232        if (!m_mapping.size()) {
233            ASSERT(ancestorLayer->renderer()->isRenderView());
234            pushMappingsToAncestor(ancestorLayer->renderer(), 0);
235        }
236
237        TemporaryChange<size_t> positionChange(m_insertionPosition, m_mapping.size());
238        bool accumulatingTransform = layer->renderer()->style()->preserves3D() || ancestorLayer->renderer()->style()->preserves3D();
239        push(renderer, toLayoutSize(layerOffset), accumulatingTransform, /*isNonUniform*/ false, /*isFixedPosition*/ false, /*hasTransform*/ false);
240        return;
241    }
242    const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? ancestorLayer->renderer() : 0;
243    pushMappingsToAncestor(renderer, ancestorRenderer);
244}
245
246void RenderGeometryMap::push(const RenderObject* renderer, const LayoutSize& offsetFromContainer, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform, LayoutSize offsetForFixedPosition)
247{
248//    fprintf(stderr, "RenderGeometryMap::push %p %d,%d isNonUniform=%d\n", renderer, offsetFromContainer.width().toInt(), offsetFromContainer.height().toInt(), isNonUniform);
249
250    ASSERT(m_insertionPosition != kNotFound);
251    ASSERT(!renderer->isRenderView() || !m_insertionPosition || m_mapCoordinatesFlags & TraverseDocumentBoundaries);
252    ASSERT(offsetForFixedPosition.isZero() || renderer->isRenderView());
253
254    m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform));
255
256    RenderGeometryMapStep& step = m_mapping[m_insertionPosition];
257    step.m_offset = offsetFromContainer;
258    step.m_offsetForFixedPosition = offsetForFixedPosition;
259
260    stepInserted(step);
261}
262
263void RenderGeometryMap::push(const RenderObject* renderer, const TransformationMatrix& t, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform, LayoutSize offsetForFixedPosition)
264{
265    ASSERT(m_insertionPosition != kNotFound);
266    ASSERT(!renderer->isRenderView() || !m_insertionPosition || m_mapCoordinatesFlags & TraverseDocumentBoundaries);
267    ASSERT(offsetForFixedPosition.isZero() || renderer->isRenderView());
268
269    m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform));
270
271    RenderGeometryMapStep& step = m_mapping[m_insertionPosition];
272    step.m_offsetForFixedPosition = offsetForFixedPosition;
273
274    if (!t.isIntegerTranslation())
275        step.m_transform = adoptPtr(new TransformationMatrix(t));
276    else
277        step.m_offset = LayoutSize(t.e(), t.f());
278
279    stepInserted(step);
280}
281
282void RenderGeometryMap::popMappingsToAncestor(const RenderLayerModelObject* ancestorRenderer)
283{
284    ASSERT(m_mapping.size());
285
286    while (m_mapping.size() && m_mapping.last().m_renderer != ancestorRenderer) {
287        stepRemoved(m_mapping.last());
288        m_mapping.removeLast();
289    }
290}
291
292void RenderGeometryMap::popMappingsToAncestor(const RenderLayer* ancestorLayer)
293{
294    const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? ancestorLayer->renderer() : 0;
295    popMappingsToAncestor(ancestorRenderer);
296}
297
298void RenderGeometryMap::stepInserted(const RenderGeometryMapStep& step)
299{
300    m_accumulatedOffset += step.m_offset;
301
302    if (step.m_isNonUniform)
303        ++m_nonUniformStepsCount;
304
305    if (step.m_transform)
306        ++m_transformedStepsCount;
307
308    if (step.m_isFixedPosition)
309        ++m_fixedStepsCount;
310}
311
312void RenderGeometryMap::stepRemoved(const RenderGeometryMapStep& step)
313{
314    m_accumulatedOffset -= step.m_offset;
315
316    if (step.m_isNonUniform) {
317        ASSERT(m_nonUniformStepsCount);
318        --m_nonUniformStepsCount;
319    }
320
321    if (step.m_transform) {
322        ASSERT(m_transformedStepsCount);
323        --m_transformedStepsCount;
324    }
325
326    if (step.m_isFixedPosition) {
327        ASSERT(m_fixedStepsCount);
328        --m_fixedStepsCount;
329    }
330}
331
332#if ENABLE(ASSERT)
333bool RenderGeometryMap::isTopmostRenderView(const RenderObject* renderer) const
334{
335    if (!renderer->isRenderView())
336        return false;
337
338    // If we're not working with multiple RenderViews, then any view is considered
339    // "topmost" (to preserve original behavior).
340    if (!(m_mapCoordinatesFlags & TraverseDocumentBoundaries))
341        return true;
342
343    return renderer->frame()->isMainFrame();
344}
345#endif
346
347} // namespace blink
348