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/Frame.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 35 namespace WebCore { 36 37 RenderGeometryMap::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 46 RenderGeometryMap::~RenderGeometryMap() 47 { 48 } 49 50 void 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 root RenderView (step 0) we want to apply its fixed position 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 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 107 FloatPoint 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 + roundedIntSize(m_accumulatedOffset); 113 else { 114 TransformState transformState(TransformState::ApplyTransformDirection, p); 115 mapToContainer(transformState, container); 116 result = transformState.lastPlanarPoint(); 117 } 118 119 #if !ASSERT_DISABLED 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->localToAbsolute(p, 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. 140 void RenderGeometryMap::dumpSteps() 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 152 FloatQuad 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 !ASSERT_DISABLED 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 || !layer->subtreeIsInvisible()) { 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 186 void 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 197 static 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 214 void 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 push(renderer, toLayoutSize(layerOffset), /*accumulatingTransform*/ true, /*isNonUniform*/ false, /*isFixedPosition*/ false, /*hasTransform*/ false); 239 return; 240 } 241 const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? ancestorLayer->renderer() : 0; 242 pushMappingsToAncestor(renderer, ancestorRenderer); 243 } 244 245 void RenderGeometryMap::push(const RenderObject* renderer, const LayoutSize& offsetFromContainer, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform, LayoutSize offsetForFixedPosition) 246 { 247 // fprintf(stderr, "RenderGeometryMap::push %p %d,%d isNonUniform=%d\n", renderer, offsetFromContainer.width().toInt(), offsetFromContainer.height().toInt(), isNonUniform); 248 249 ASSERT(m_insertionPosition != kNotFound); 250 ASSERT(!renderer->isRenderView() || !m_insertionPosition || m_mapCoordinatesFlags & TraverseDocumentBoundaries); 251 ASSERT(offsetForFixedPosition.isZero() || renderer->isRenderView()); 252 253 m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform)); 254 255 RenderGeometryMapStep& step = m_mapping[m_insertionPosition]; 256 step.m_offset = offsetFromContainer; 257 step.m_offsetForFixedPosition = offsetForFixedPosition; 258 259 stepInserted(step); 260 } 261 262 void RenderGeometryMap::push(const RenderObject* renderer, const TransformationMatrix& t, bool accumulatingTransform, bool isNonUniform, bool isFixedPosition, bool hasTransform, LayoutSize offsetForFixedPosition) 263 { 264 ASSERT(m_insertionPosition != kNotFound); 265 ASSERT(!renderer->isRenderView() || !m_insertionPosition || m_mapCoordinatesFlags & TraverseDocumentBoundaries); 266 ASSERT(offsetForFixedPosition.isZero() || renderer->isRenderView()); 267 268 m_mapping.insert(m_insertionPosition, RenderGeometryMapStep(renderer, accumulatingTransform, isNonUniform, isFixedPosition, hasTransform)); 269 270 RenderGeometryMapStep& step = m_mapping[m_insertionPosition]; 271 step.m_offsetForFixedPosition = offsetForFixedPosition; 272 273 if (!t.isIntegerTranslation()) 274 step.m_transform = adoptPtr(new TransformationMatrix(t)); 275 else 276 step.m_offset = LayoutSize(t.e(), t.f()); 277 278 stepInserted(step); 279 } 280 281 void RenderGeometryMap::popMappingsToAncestor(const RenderLayerModelObject* ancestorRenderer) 282 { 283 ASSERT(m_mapping.size()); 284 285 while (m_mapping.size() && m_mapping.last().m_renderer != ancestorRenderer) { 286 stepRemoved(m_mapping.last()); 287 m_mapping.removeLast(); 288 } 289 } 290 291 void RenderGeometryMap::popMappingsToAncestor(const RenderLayer* ancestorLayer) 292 { 293 const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? ancestorLayer->renderer() : 0; 294 popMappingsToAncestor(ancestorRenderer); 295 } 296 297 void RenderGeometryMap::stepInserted(const RenderGeometryMapStep& step) 298 { 299 m_accumulatedOffset += step.m_offset; 300 301 if (step.m_isNonUniform) 302 ++m_nonUniformStepsCount; 303 304 if (step.m_transform) 305 ++m_transformedStepsCount; 306 307 if (step.m_isFixedPosition) 308 ++m_fixedStepsCount; 309 } 310 311 void RenderGeometryMap::stepRemoved(const RenderGeometryMapStep& step) 312 { 313 m_accumulatedOffset -= step.m_offset; 314 315 if (step.m_isNonUniform) { 316 ASSERT(m_nonUniformStepsCount); 317 --m_nonUniformStepsCount; 318 } 319 320 if (step.m_transform) { 321 ASSERT(m_transformedStepsCount); 322 --m_transformedStepsCount; 323 } 324 325 if (step.m_isFixedPosition) { 326 ASSERT(m_fixedStepsCount); 327 --m_fixedStepsCount; 328 } 329 } 330 331 #if !ASSERT_DISABLED 332 bool RenderGeometryMap::isTopmostRenderView(const RenderObject* renderer) const 333 { 334 if (!renderer->isRenderView()) 335 return false; 336 337 // If we're not working with multiple RenderViews, then any view is considered 338 // "topmost" (to preserve original behavior). 339 if (!(m_mapCoordinatesFlags & TraverseDocumentBoundaries)) 340 return true; 341 342 return renderer->frame()->isMainFrame(); 343 } 344 #endif 345 346 } // namespace WebCore 347