2 * Copyright (C) 2012 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "core/layout/LayoutGeometryMap.h"
29 #include "core/frame/LocalFrame.h"
30 #include "core/layout/LayoutView.h"
31 #include "core/paint/DeprecatedPaintLayer.h"
32 #include "platform/geometry/TransformState.h"
33 #include "wtf/TemporaryChange.h"
37 LayoutGeometryMap::LayoutGeometryMap(MapCoordinatesFlags flags
)
38 : m_insertionPosition(kNotFound
)
39 , m_nonUniformStepsCount(0)
40 , m_transformedStepsCount(0)
41 , m_fixedStepsCount(0)
42 , m_mapCoordinatesFlags(flags
)
46 LayoutGeometryMap::~LayoutGeometryMap()
50 void LayoutGeometryMap::mapToContainer(TransformState
& transformState
, const LayoutBoxModelObject
* container
) const
52 // If the mapping includes something like columns, we have to go via layoutObjects.
53 if (hasNonUniformStep()) {
54 m_mapping
.last().m_layoutObject
->mapLocalToContainer(container
, transformState
, ApplyContainerFlip
| m_mapCoordinatesFlags
);
55 transformState
.flatten();
61 bool foundContainer
= !container
|| (m_mapping
.size() && m_mapping
[0].m_layoutObject
== container
);
64 for (int i
= m_mapping
.size() - 1; i
>= 0; --i
) {
65 const LayoutGeometryMapStep
& currentStep
= m_mapping
[i
];
67 // If container is the root LayoutView (step 0) we want to apply its fixed position offset.
68 if (i
> 0 && currentStep
.m_layoutObject
== container
) {
70 foundContainer
= true;
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
)
80 else if (currentStep
.m_isFixedPosition
)
83 ASSERT(!i
== isTopmostLayoutView(currentStep
.m_layoutObject
));
86 // A null container indicates mapping through the root LayoutView, so including its transform (the page scale).
87 if (!container
&& currentStep
.m_transform
)
88 transformState
.applyTransform(*currentStep
.m_transform
.get());
90 TransformState::TransformAccumulation accumulate
= currentStep
.m_accumulatingTransform
? TransformState::AccumulateTransform
: TransformState::FlattenTransform
;
91 if (currentStep
.m_transform
)
92 transformState
.applyTransform(*currentStep
.m_transform
.get(), accumulate
);
94 transformState
.move(currentStep
.m_offset
.width(), currentStep
.m_offset
.height(), accumulate
);
97 if (inFixed
&& !currentStep
.m_offsetForFixedPosition
.isZero()) {
98 ASSERT(currentStep
.m_layoutObject
->isLayoutView());
99 transformState
.move(currentStep
.m_offsetForFixedPosition
);
103 ASSERT(foundContainer
);
104 transformState
.flatten();
107 FloatPoint
LayoutGeometryMap::mapToContainer(const FloatPoint
& p
, const LayoutBoxModelObject
* container
) const
111 if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container
|| (m_mapping
.size() && container
== m_mapping
[0].m_layoutObject
))) {
112 result
= p
+ m_accumulatedOffset
;
114 TransformState
transformState(TransformState::ApplyTransformDirection
, p
);
115 mapToContainer(transformState
, container
);
116 result
= transformState
.lastPlanarPoint();
120 if (m_mapping
.size() > 0) {
121 const LayoutObject
* lastLayoutObject
= m_mapping
.last().m_layoutObject
;
122 const DeprecatedPaintLayer
* layer
= lastLayoutObject
->enclosingLayer();
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 layoutObjectMappedResult
= lastLayoutObject
->localToContainerPoint(p
, container
, m_mapCoordinatesFlags
);
130 ASSERT(roundedIntPoint(layoutObjectMappedResult
) == roundedIntPoint(result
));
139 // Handy function to call from gdb while debugging mismatched point/rect errors.
140 void LayoutGeometryMap::dumpSteps() const
142 fprintf(stderr
, "LayoutGeometryMap::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
,
145 m_mapping
[i
].m_layoutObject
->debugName().ascii().data(),
146 m_mapping
[i
].m_offset
.width().toInt(),
147 m_mapping
[i
].m_offset
.height().toInt());
148 if (m_mapping
[i
].m_hasTransform
)
149 fprintf(stderr
, " hasTransform");
150 fprintf(stderr
, "\n");
155 FloatQuad
LayoutGeometryMap::mapToContainer(const FloatRect
& rect
, const LayoutBoxModelObject
* container
) const
159 if (!hasFixedPositionStep() && !hasTransformStep() && !hasNonUniformStep() && (!container
|| (m_mapping
.size() && container
== m_mapping
[0].m_layoutObject
))) {
161 result
.move(m_accumulatedOffset
);
163 TransformState
transformState(TransformState::ApplyTransformDirection
, rect
.center(), rect
);
164 mapToContainer(transformState
, container
);
165 result
= transformState
.lastPlanarQuad();
169 if (m_mapping
.size() > 0) {
170 const LayoutObject
* lastLayoutObject
= m_mapping
.last().m_layoutObject
;
171 const DeprecatedPaintLayer
* layer
= lastLayoutObject
->enclosingLayer();
173 // Bounds for invisible layers are intentionally not calculated, and are
174 // therefore not necessarily expected to be correct here. This is ok,
175 // because they will be recomputed if the layer becomes visible.
176 if (!layer
->subtreeIsInvisible() && lastLayoutObject
->style()->visibility() == VISIBLE
) {
177 FloatRect layoutObjectMappedResult
= lastLayoutObject
->localToContainerQuad(rect
, container
, m_mapCoordinatesFlags
).boundingBox();
179 // Inspector creates layoutObjects with negative width <https://bugs.webkit.org/show_bug.cgi?id=87194>.
180 // Taking FloatQuad bounds avoids spurious assertions because of that.
181 ASSERT(enclosingIntRect(layoutObjectMappedResult
) == enclosingIntRect(result
.boundingBox()));
189 void LayoutGeometryMap::pushMappingsToAncestor(const LayoutObject
* layoutObject
, const LayoutBoxModelObject
* ancestorLayoutObject
)
191 // We need to push mappings in reverse order here, so do insertions rather than appends.
192 TemporaryChange
<size_t> positionChange(m_insertionPosition
, m_mapping
.size());
194 layoutObject
= layoutObject
->pushMappingToContainer(ancestorLayoutObject
, *this);
195 } while (layoutObject
&& layoutObject
!= ancestorLayoutObject
);
197 ASSERT(m_mapping
.isEmpty() || isTopmostLayoutView(m_mapping
[0].m_layoutObject
));
200 static bool canMapBetweenLayoutObjects(const LayoutObject
* layoutObject
, const LayoutObject
* ancestor
)
202 for (const LayoutObject
* current
= layoutObject
; ; current
= current
->parent()) {
203 const ComputedStyle
& style
= current
->styleRef();
204 if (style
.position() == FixedPosition
|| style
.isFlippedBlocksWritingMode())
207 if (current
->hasTransformRelatedProperty() || current
->isLayoutFlowThread() || current
->isSVGRoot())
210 if (current
== ancestor
)
217 void LayoutGeometryMap::pushMappingsToAncestor(const DeprecatedPaintLayer
* layer
, const DeprecatedPaintLayer
* ancestorLayer
)
219 const LayoutObject
* layoutObject
= layer
->layoutObject();
221 bool crossDocument
= ancestorLayer
&& layer
->layoutObject()->frame() != ancestorLayer
->layoutObject()->frame();
222 ASSERT(!crossDocument
|| m_mapCoordinatesFlags
& TraverseDocumentBoundaries
);
224 // We have to visit all the layoutObjects to detect flipped blocks. This might defeat the gains
225 // from mapping via layers.
226 bool canConvertInLayerTree
= (ancestorLayer
&& !crossDocument
) ? canMapBetweenLayoutObjects(layer
->layoutObject(), ancestorLayer
->layoutObject()) : false;
228 // fprintf(stderr, "LayoutGeometryMap::pushMappingsToAncestor from layer %p to layer %p, canConvertInLayerTree=%d\n", layer, ancestorLayer, canConvertInLayerTree);
230 if (canConvertInLayerTree
) {
231 LayoutPoint layerOffset
;
232 layer
->convertToLayerCoords(ancestorLayer
, layerOffset
);
234 // The LayoutView must be pushed first.
235 if (!m_mapping
.size()) {
236 ASSERT(ancestorLayer
->layoutObject()->isLayoutView());
237 pushMappingsToAncestor(ancestorLayer
->layoutObject(), 0);
240 TemporaryChange
<size_t> positionChange(m_insertionPosition
, m_mapping
.size());
241 bool accumulatingTransform
= layer
->layoutObject()->style()->preserves3D() || ancestorLayer
->layoutObject()->style()->preserves3D();
242 push(layoutObject
, toLayoutSize(layerOffset
), accumulatingTransform
, /*isNonUniform*/ false, /*isFixedPosition*/ false, /*hasTransform*/ false);
245 const LayoutBoxModelObject
* ancestorLayoutObject
= ancestorLayer
? ancestorLayer
->layoutObject() : 0;
246 pushMappingsToAncestor(layoutObject
, ancestorLayoutObject
);
249 void LayoutGeometryMap::push(const LayoutObject
* layoutObject
, const LayoutSize
& offsetFromContainer
, bool accumulatingTransform
, bool isNonUniform
, bool isFixedPosition
, bool hasTransform
, LayoutSize offsetForFixedPosition
)
251 // fprintf(stderr, "LayoutGeometryMap::push %p %d,%d isNonUniform=%d\n", layoutObject, offsetFromContainer.width().toInt(), offsetFromContainer.height().toInt(), isNonUniform);
253 ASSERT(m_insertionPosition
!= kNotFound
);
254 ASSERT(!layoutObject
->isLayoutView() || !m_insertionPosition
|| m_mapCoordinatesFlags
& TraverseDocumentBoundaries
);
255 ASSERT(offsetForFixedPosition
.isZero() || layoutObject
->isLayoutView());
257 m_mapping
.insert(m_insertionPosition
, LayoutGeometryMapStep(layoutObject
, accumulatingTransform
, isNonUniform
, isFixedPosition
, hasTransform
));
259 LayoutGeometryMapStep
& step
= m_mapping
[m_insertionPosition
];
260 step
.m_offset
= offsetFromContainer
;
261 step
.m_offsetForFixedPosition
= offsetForFixedPosition
;
266 void LayoutGeometryMap::push(const LayoutObject
* layoutObject
, const TransformationMatrix
& t
, bool accumulatingTransform
, bool isNonUniform
, bool isFixedPosition
, bool hasTransform
, LayoutSize offsetForFixedPosition
)
268 ASSERT(m_insertionPosition
!= kNotFound
);
269 ASSERT(!layoutObject
->isLayoutView() || !m_insertionPosition
|| m_mapCoordinatesFlags
& TraverseDocumentBoundaries
);
270 ASSERT(offsetForFixedPosition
.isZero() || layoutObject
->isLayoutView());
272 m_mapping
.insert(m_insertionPosition
, LayoutGeometryMapStep(layoutObject
, accumulatingTransform
, isNonUniform
, isFixedPosition
, hasTransform
));
274 LayoutGeometryMapStep
& step
= m_mapping
[m_insertionPosition
];
275 step
.m_offsetForFixedPosition
= offsetForFixedPosition
;
277 if (!t
.isIntegerTranslation())
278 step
.m_transform
= adoptPtr(new TransformationMatrix(t
));
280 step
.m_offset
= LayoutSize(t
.e(), t
.f());
285 void LayoutGeometryMap::popMappingsToAncestor(const LayoutBoxModelObject
* ancestorLayoutObject
)
287 ASSERT(m_mapping
.size());
289 while (m_mapping
.size() && m_mapping
.last().m_layoutObject
!= ancestorLayoutObject
) {
290 stepRemoved(m_mapping
.last());
291 m_mapping
.removeLast();
295 void LayoutGeometryMap::popMappingsToAncestor(const DeprecatedPaintLayer
* ancestorLayer
)
297 const LayoutBoxModelObject
* ancestorLayoutObject
= ancestorLayer
? ancestorLayer
->layoutObject() : 0;
298 popMappingsToAncestor(ancestorLayoutObject
);
301 void LayoutGeometryMap::stepInserted(const LayoutGeometryMapStep
& step
)
303 m_accumulatedOffset
+= step
.m_offset
;
305 if (step
.m_isNonUniform
)
306 ++m_nonUniformStepsCount
;
308 if (step
.m_transform
)
309 ++m_transformedStepsCount
;
311 if (step
.m_isFixedPosition
)
315 void LayoutGeometryMap::stepRemoved(const LayoutGeometryMapStep
& step
)
317 m_accumulatedOffset
-= step
.m_offset
;
319 if (step
.m_isNonUniform
) {
320 ASSERT(m_nonUniformStepsCount
);
321 --m_nonUniformStepsCount
;
324 if (step
.m_transform
) {
325 ASSERT(m_transformedStepsCount
);
326 --m_transformedStepsCount
;
329 if (step
.m_isFixedPosition
) {
330 ASSERT(m_fixedStepsCount
);
336 bool LayoutGeometryMap::isTopmostLayoutView(const LayoutObject
* layoutObject
) const
338 if (!layoutObject
->isLayoutView())
341 // If we're not working with multiple LayoutViews, then any view is considered
342 // "topmost" (to preserve original behavior).
343 if (!(m_mapCoordinatesFlags
& TraverseDocumentBoundaries
))
346 return layoutObject
->frame()->isMainFrame();