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