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 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_ENABLED 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_ENABLED 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 + m_accumulatedOffset; 113 else { 114 TransformState transformState(TransformState::ApplyTransformDirection, p); 115 mapToContainer(transformState, container); 116 result = transformState.lastPlanarPoint(); 117 } 118 119 #if ASSERT_ENABLED 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. 140 void 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 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_ENABLED 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 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 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 246 void 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 263 void 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 282 void 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 292 void RenderGeometryMap::popMappingsToAncestor(const RenderLayer* ancestorLayer) 293 { 294 const RenderLayerModelObject* ancestorRenderer = ancestorLayer ? ancestorLayer->renderer() : 0; 295 popMappingsToAncestor(ancestorRenderer); 296 } 297 298 void 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 312 void 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 ASSERT_ENABLED 333 bool 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 WebCore 348