| /* |
| * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Apple Inc. All rights reserved. |
| * |
| * Portions are Copyright (C) 1998 Netscape Communications Corporation. |
| * |
| * Other contributors: |
| * Robert O'Callahan <roc+@cs.cmu.edu> |
| * David Baron <dbaron@fas.harvard.edu> |
| * Christian Biesinger <cbiesinger@web.de> |
| * Randall Jesup <rjesup@wgate.com> |
| * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> |
| * Josh Soref <timeless@mac.com> |
| * Boris Zbarsky <bzbarsky@mit.edu> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| * Alternatively, the contents of this file may be used under the terms |
| * of either the Mozilla Public License Version 1.1, found at |
| * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
| * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
| * (the "GPL"), in which case the provisions of the MPL or the GPL are |
| * applicable instead of those above. If you wish to allow use of your |
| * version of this file only under the terms of one of those two |
| * licenses (the MPL or the GPL) and not to allow others to use your |
| * version of this file under the LGPL, indicate your decision by |
| * deletingthe provisions above and replace them with the notice and |
| * other provisions required by the MPL or the GPL, as the case may be. |
| * If you do not delete the provisions above, a recipient may use your |
| * version of this file under any of the LGPL, the MPL or the GPL. |
| */ |
| |
| #include "sky/engine/config.h" |
| #include "sky/engine/core/rendering/RenderLayer.h" |
| |
| #include "gen/sky/core/CSSPropertyNames.h" |
| #include "gen/sky/platform/RuntimeEnabledFeatures.h" |
| #include "sky/engine/core/dom/Document.h" |
| #include "sky/engine/core/dom/shadow/ShadowRoot.h" |
| #include "sky/engine/core/frame/FrameView.h" |
| #include "sky/engine/core/frame/LocalFrame.h" |
| #include "sky/engine/core/frame/Settings.h" |
| #include "sky/engine/core/page/Page.h" |
| #include "sky/engine/core/rendering/FilterEffectRenderer.h" |
| #include "sky/engine/core/rendering/HitTestRequest.h" |
| #include "sky/engine/core/rendering/HitTestResult.h" |
| #include "sky/engine/core/rendering/HitTestingTransformState.h" |
| #include "sky/engine/core/rendering/RenderGeometryMap.h" |
| #include "sky/engine/core/rendering/RenderInline.h" |
| #include "sky/engine/core/rendering/RenderTreeAsText.h" |
| #include "sky/engine/core/rendering/RenderView.h" |
| #include "sky/engine/platform/LengthFunctions.h" |
| #include "sky/engine/platform/Partitions.h" |
| #include "sky/engine/platform/TraceEvent.h" |
| #include "sky/engine/platform/geometry/FloatPoint3D.h" |
| #include "sky/engine/platform/geometry/FloatRect.h" |
| #include "sky/engine/platform/geometry/TransformState.h" |
| #include "sky/engine/platform/graphics/GraphicsContextStateSaver.h" |
| #include "sky/engine/platform/graphics/filters/ReferenceFilter.h" |
| #include "sky/engine/platform/graphics/filters/SourceGraphic.h" |
| #include "sky/engine/platform/transforms/ScaleTransformOperation.h" |
| #include "sky/engine/platform/transforms/TransformationMatrix.h" |
| #include "sky/engine/platform/transforms/TranslateTransformOperation.h" |
| #include "sky/engine/public/platform/Platform.h" |
| #include "sky/engine/wtf/StdLibExtras.h" |
| #include "sky/engine/wtf/text/CString.h" |
| |
| namespace blink { |
| |
| RenderLayer::RenderLayer(RenderLayerModelObject* renderer, LayerType type) |
| : m_layerType(type) |
| , m_hasSelfPaintingLayerDescendant(false) |
| , m_hasSelfPaintingLayerDescendantDirty(false) |
| , m_isRootLayer(renderer->isRenderView()) |
| , m_usedTransparency(false) |
| , m_3DTransformedDescendantStatusDirty(true) |
| , m_has3DTransformedDescendant(false) |
| , m_containsDirtyOverlayScrollbars(false) |
| , m_hasFilterInfo(false) |
| , m_needsAncestorDependentCompositingInputsUpdate(true) |
| , m_needsDescendantDependentCompositingInputsUpdate(true) |
| , m_childNeedsCompositingInputsUpdate(true) |
| , m_hasCompositingDescendant(false) |
| , m_lostGroupedMapping(false) |
| , m_renderer(renderer) |
| , m_parent(0) |
| , m_previous(0) |
| , m_next(0) |
| , m_first(0) |
| , m_last(0) |
| , m_staticInlinePosition(0) |
| , m_staticBlockPosition(0) |
| , m_clipper(*renderer) |
| { |
| updateStackingNode(); |
| |
| m_isSelfPaintingLayer = shouldBeSelfPaintingLayer(); |
| |
| updateScrollableArea(); |
| } |
| |
| RenderLayer::~RenderLayer() |
| { |
| removeFilterInfoIfNeeded(); |
| } |
| |
| String RenderLayer::debugName() const |
| { |
| return renderer()->debugName(); |
| } |
| |
| bool RenderLayer::paintsWithFilters() const |
| { |
| // FIXME(sky): Remove |
| return renderer()->hasFilter(); |
| } |
| |
| bool RenderLayer::requiresFullLayerImageForFilters() const |
| { |
| if (!paintsWithFilters()) |
| return false; |
| FilterEffectRenderer* filter = filterRenderer(); |
| return filter ? filter->hasFilterThatMovesPixels() : false; |
| } |
| |
| LayoutSize RenderLayer::subpixelAccumulation() const |
| { |
| return m_subpixelAccumulation; |
| } |
| |
| void RenderLayer::setSubpixelAccumulation(const LayoutSize& size) |
| { |
| m_subpixelAccumulation = size; |
| } |
| |
| void RenderLayer::updateLayerPositionsAfterLayout() |
| { |
| TRACE_EVENT0("blink", "RenderLayer::updateLayerPositionsAfterLayout"); |
| |
| m_clipper.clearClipRectsIncludingDescendants(); |
| } |
| |
| void RenderLayer::updateHasSelfPaintingLayerDescendant() const |
| { |
| ASSERT(m_hasSelfPaintingLayerDescendantDirty); |
| |
| m_hasSelfPaintingLayerDescendant = false; |
| |
| for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { |
| if (child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant()) { |
| m_hasSelfPaintingLayerDescendant = true; |
| break; |
| } |
| } |
| |
| m_hasSelfPaintingLayerDescendantDirty = false; |
| } |
| |
| void RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus() |
| { |
| for (RenderLayer* layer = this; layer; layer = layer->parent()) { |
| layer->m_hasSelfPaintingLayerDescendantDirty = true; |
| // If we have reached a self-painting layer, we know our parent should have a self-painting descendant |
| // in this case, there is no need to dirty our ancestors further. |
| if (layer->isSelfPaintingLayer()) { |
| ASSERT(!parent() || parent()->m_hasSelfPaintingLayerDescendantDirty || parent()->m_hasSelfPaintingLayerDescendant); |
| break; |
| } |
| } |
| } |
| |
| bool RenderLayer::scrollsWithRespectTo(const RenderLayer* other) const |
| { |
| return ancestorScrollingLayer() != other->ancestorScrollingLayer(); |
| } |
| |
| void RenderLayer::updateTransformationMatrix() |
| { |
| if (m_transform) { |
| RenderBox* box = renderBox(); |
| ASSERT(box); |
| m_transform->makeIdentity(); |
| box->style()->applyTransform(*m_transform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::IncludeTransformOrigin); |
| makeMatrixRenderable(*m_transform); |
| } |
| } |
| |
| void RenderLayer::updateTransform(const RenderStyle* oldStyle, RenderStyle* newStyle) |
| { |
| if (oldStyle && newStyle->transformDataEquivalent(*oldStyle)) |
| return; |
| |
| // hasTransform() on the renderer is also true when there is transform-style: preserve-3d or perspective set, |
| // so check style too. |
| bool hasTransform = renderer()->hasTransform() && newStyle->hasTransform(); |
| bool had3DTransform = has3DTransform(); |
| |
| bool hadTransform = m_transform; |
| if (hasTransform != hadTransform) { |
| if (hasTransform) |
| m_transform = adoptPtr(new TransformationMatrix); |
| else |
| m_transform.clear(); |
| |
| // Layers with transforms act as clip rects roots, so clear the cached clip rects here. |
| m_clipper.clearClipRectsIncludingDescendants(); |
| } else if (hasTransform) { |
| m_clipper.clearClipRectsIncludingDescendants(AbsoluteClipRects); |
| } |
| |
| updateTransformationMatrix(); |
| |
| if (had3DTransform != has3DTransform()) |
| dirty3DTransformedDescendantStatus(); |
| } |
| |
| static RenderLayer* enclosingLayerForContainingBlock(RenderLayer* layer) |
| { |
| if (RenderObject* containingBlock = layer->renderer()->containingBlock()) |
| return containingBlock->enclosingLayer(); |
| return 0; |
| } |
| |
| RenderLayer* RenderLayer::renderingContextRoot() |
| { |
| RenderLayer* renderingContext = 0; |
| |
| if (shouldPreserve3D()) |
| renderingContext = this; |
| |
| for (RenderLayer* current = enclosingLayerForContainingBlock(this); current && current->shouldPreserve3D(); current = enclosingLayerForContainingBlock(current)) |
| renderingContext = current; |
| |
| return renderingContext; |
| } |
| |
| TransformationMatrix RenderLayer::currentTransform(RenderStyle::ApplyTransformOrigin applyOrigin) const |
| { |
| if (!m_transform) |
| return TransformationMatrix(); |
| |
| // m_transform includes transform-origin, so we need to recompute the transform here. |
| if (applyOrigin == RenderStyle::ExcludeTransformOrigin) { |
| RenderBox* box = renderBox(); |
| TransformationMatrix currTransform; |
| box->style()->applyTransform(currTransform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::ExcludeTransformOrigin); |
| makeMatrixRenderable(currTransform); |
| return currTransform; |
| } |
| |
| return *m_transform; |
| } |
| |
| TransformationMatrix RenderLayer::renderableTransform() const |
| { |
| return m_transform ? *m_transform : TransformationMatrix(); |
| } |
| |
| RenderLayer* RenderLayer::enclosingOverflowClipLayer(IncludeSelfOrNot includeSelf) const |
| { |
| const RenderLayer* layer = (includeSelf == IncludeSelf) ? this : parent(); |
| while (layer) { |
| if (layer->renderer()->hasOverflowClip()) |
| return const_cast<RenderLayer*>(layer); |
| |
| layer = layer->parent(); |
| } |
| return 0; |
| } |
| |
| void RenderLayer::dirty3DTransformedDescendantStatus() |
| { |
| RenderLayerStackingNode* stackingNode = m_stackingNode->ancestorStackingContextNode(); |
| if (!stackingNode) |
| return; |
| |
| stackingNode->layer()->m_3DTransformedDescendantStatusDirty = true; |
| |
| // This propagates up through preserve-3d hierarchies to the enclosing flattening layer. |
| // Note that preserves3D() creates stacking context, so we can just run up the stacking containers. |
| while (stackingNode && stackingNode->layer()->preserves3D()) { |
| stackingNode->layer()->m_3DTransformedDescendantStatusDirty = true; |
| stackingNode = stackingNode->ancestorStackingContextNode(); |
| } |
| } |
| |
| // Return true if this layer or any preserve-3d descendants have 3d. |
| bool RenderLayer::update3DTransformedDescendantStatus() |
| { |
| if (m_3DTransformedDescendantStatusDirty) { |
| m_has3DTransformedDescendant = false; |
| |
| m_stackingNode->updateZOrderLists(); |
| |
| // Transformed or preserve-3d descendants can only be in the z-order lists, not |
| // in the normal flow list, so we only need to check those. |
| RenderLayerStackingNodeIterator iterator(*m_stackingNode.get(), PositiveZOrderChildren | NegativeZOrderChildren); |
| while (RenderLayerStackingNode* node = iterator.next()) |
| m_has3DTransformedDescendant |= node->layer()->update3DTransformedDescendantStatus(); |
| |
| m_3DTransformedDescendantStatusDirty = false; |
| } |
| |
| // If we live in a 3d hierarchy, then the layer at the root of that hierarchy needs |
| // the m_has3DTransformedDescendant set. |
| if (preserves3D()) |
| return has3DTransform() || m_has3DTransformedDescendant; |
| |
| return has3DTransform(); |
| } |
| |
| IntSize RenderLayer::size() const |
| { |
| if (renderer()->isInline() && renderer()->isRenderInline()) |
| return toRenderInline(renderer())->linesBoundingBox().size(); |
| |
| // FIXME: Is snapping the size really needed here? |
| if (RenderBox* box = renderBox()) |
| return pixelSnappedIntSize(box->size(), box->location()); |
| |
| return IntSize(); |
| } |
| |
| LayoutPoint RenderLayer::location() const |
| { |
| LayoutPoint localPoint; |
| LayoutSize inlineBoundingBoxOffset; // We don't put this into the RenderLayer x/y for inlines, so we need to subtract it out when done. |
| |
| if (renderer()->isInline() && renderer()->isRenderInline()) { |
| RenderInline* inlineFlow = toRenderInline(renderer()); |
| IntRect lineBox = inlineFlow->linesBoundingBox(); |
| inlineBoundingBoxOffset = toSize(lineBox.location()); |
| localPoint += inlineBoundingBoxOffset; |
| } else if (RenderBox* box = renderBox()) { |
| localPoint += box->locationOffset(); |
| } |
| |
| if (!renderer()->isOutOfFlowPositioned() && renderer()->parent()) { |
| // We must adjust our position by walking up the render tree looking for the |
| // nearest enclosing object with a layer. |
| RenderObject* curr = renderer()->parent(); |
| while (curr && !curr->hasLayer()) { |
| if (curr->isBox()) { |
| // Rows and cells share the same coordinate space (that of the section). |
| // Omit them when computing our xpos/ypos. |
| localPoint += toRenderBox(curr)->locationOffset(); |
| } |
| curr = curr->parent(); |
| } |
| } |
| |
| // Subtract our parent's scroll offset. |
| if (renderer()->isOutOfFlowPositioned() && enclosingPositionedAncestor()) { |
| RenderLayer* positionedParent = enclosingPositionedAncestor(); |
| |
| // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. |
| if (positionedParent->renderer()->hasOverflowClip()) { |
| LayoutSize offset = positionedParent->renderBox()->scrolledContentOffset(); |
| localPoint -= offset; |
| } |
| |
| if (positionedParent->renderer()->isRelPositioned() && positionedParent->renderer()->isRenderInline()) { |
| LayoutSize offset = toRenderInline(positionedParent->renderer())->offsetForInFlowPositionedInline(*toRenderBox(renderer())); |
| localPoint += offset; |
| } |
| } else if (parent()) { |
| if (parent()->renderer()->hasOverflowClip()) { |
| IntSize scrollOffset = parent()->renderBox()->scrolledContentOffset(); |
| localPoint -= scrollOffset; |
| } |
| } |
| |
| localPoint.move(offsetForInFlowPosition()); |
| |
| // FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the renderers. |
| localPoint -= inlineBoundingBoxOffset; |
| |
| return localPoint; |
| } |
| |
| const LayoutSize RenderLayer::offsetForInFlowPosition() const |
| { |
| return renderer()->isRelPositioned() ? toRenderBoxModelObject(renderer())->offsetForInFlowPosition() : LayoutSize(); |
| } |
| |
| TransformationMatrix RenderLayer::perspectiveTransform() const |
| { |
| if (!renderer()->hasTransform()) |
| return TransformationMatrix(); |
| |
| RenderStyle* style = renderer()->style(); |
| if (!style->hasPerspective()) |
| return TransformationMatrix(); |
| |
| // Maybe fetch the perspective from the backing? |
| const IntRect borderBox = toRenderBox(renderer())->pixelSnappedBorderBoxRect(); |
| const float boxWidth = borderBox.width(); |
| const float boxHeight = borderBox.height(); |
| |
| float perspectiveOriginX = floatValueForLength(style->perspectiveOriginX(), boxWidth); |
| float perspectiveOriginY = floatValueForLength(style->perspectiveOriginY(), boxHeight); |
| |
| // A perspective origin of 0,0 makes the vanishing point in the center of the element. |
| // We want it to be in the top-left, so subtract half the height and width. |
| perspectiveOriginX -= boxWidth / 2.0f; |
| perspectiveOriginY -= boxHeight / 2.0f; |
| |
| TransformationMatrix t; |
| t.translate(perspectiveOriginX, perspectiveOriginY); |
| t.applyPerspective(style->perspective()); |
| t.translate(-perspectiveOriginX, -perspectiveOriginY); |
| |
| return t; |
| } |
| |
| FloatPoint RenderLayer::perspectiveOrigin() const |
| { |
| if (!renderer()->hasTransform()) |
| return FloatPoint(); |
| |
| const LayoutRect borderBox = toRenderBox(renderer())->borderBoxRect(); |
| RenderStyle* style = renderer()->style(); |
| |
| return FloatPoint(floatValueForLength(style->perspectiveOriginX(), borderBox.width().toFloat()), floatValueForLength(style->perspectiveOriginY(), borderBox.height().toFloat())); |
| } |
| |
| RenderLayer* RenderLayer::enclosingPositionedAncestor() const |
| { |
| RenderLayer* curr = parent(); |
| while (curr && !curr->isPositionedContainer()) |
| curr = curr->parent(); |
| |
| return curr; |
| } |
| |
| RenderLayer* RenderLayer::enclosingTransformedAncestor() const |
| { |
| RenderLayer* curr = parent(); |
| while (curr && !curr->isRootLayer() && !curr->renderer()->hasTransform()) |
| curr = curr->parent(); |
| |
| return curr; |
| } |
| |
| LayoutPoint RenderLayer::computeOffsetFromTransformedAncestor() const |
| { |
| const AncestorDependentCompositingInputs& properties = ancestorDependentCompositingInputs(); |
| |
| TransformState transformState(TransformState::ApplyTransformDirection, FloatPoint()); |
| // FIXME: add a test that checks flipped writing mode and ApplyContainerFlip are correct. |
| renderer()->mapLocalToContainer(properties.transformAncestor ? properties.transformAncestor->renderer() : 0, transformState, ApplyContainerFlip); |
| transformState.flatten(); |
| return LayoutPoint(transformState.lastPlanarPoint()); |
| } |
| |
| const RenderLayer* RenderLayer::compositingContainer() const |
| { |
| if (stackingNode()->isNormalFlowOnly()) |
| return parent(); |
| if (RenderLayerStackingNode* ancestorStackingNode = stackingNode()->ancestorStackingContextNode()) |
| return ancestorStackingNode->layer(); |
| return 0; |
| } |
| |
| RenderLayer* RenderLayer::enclosingFilterLayer(IncludeSelfOrNot includeSelf) const |
| { |
| const RenderLayer* curr = (includeSelf == IncludeSelf) ? this : parent(); |
| for (; curr; curr = curr->parent()) { |
| if (curr->requiresFullLayerImageForFilters()) |
| return const_cast<RenderLayer*>(curr); |
| } |
| |
| return 0; |
| } |
| |
| void RenderLayer::updateAncestorDependentCompositingInputs(const AncestorDependentCompositingInputs& compositingInputs) |
| { |
| m_ancestorDependentCompositingInputs = compositingInputs; |
| m_needsAncestorDependentCompositingInputsUpdate = false; |
| } |
| |
| void RenderLayer::updateDescendantDependentCompositingInputs(const DescendantDependentCompositingInputs& compositingInputs) |
| { |
| m_descendantDependentCompositingInputs = compositingInputs; |
| m_needsDescendantDependentCompositingInputsUpdate = false; |
| } |
| |
| void RenderLayer::setHasCompositingDescendant(bool hasCompositingDescendant) |
| { |
| // FIXME(sky): Remove |
| m_hasCompositingDescendant = hasCompositingDescendant; |
| } |
| |
| |
| bool RenderLayer::hasAncestorWithFilterOutsets() const |
| { |
| for (const RenderLayer* curr = this; curr; curr = curr->parent()) { |
| RenderLayerModelObject* renderer = curr->renderer(); |
| if (renderer->style()->hasFilterOutsets()) |
| return true; |
| } |
| return false; |
| } |
| |
| RenderLayer* RenderLayer::transparentPaintingAncestor() |
| { |
| for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { |
| if (curr->isTransparent()) |
| return curr; |
| } |
| return 0; |
| } |
| |
| enum TransparencyClipBoxBehavior { |
| PaintingTransparencyClipBox, |
| HitTestingTransparencyClipBox |
| }; |
| |
| enum TransparencyClipBoxMode { |
| DescendantsOfTransparencyClipBox, |
| RootOfTransparencyClipBox |
| }; |
| |
| static LayoutRect transparencyClipBox(const RenderLayer*, const RenderLayer* rootLayer, TransparencyClipBoxBehavior, TransparencyClipBoxMode, const LayoutSize& subPixelAccumulation); |
| |
| static void expandClipRectForDescendantsAndReflection(LayoutRect& clipRect, const RenderLayer* layer, const RenderLayer* rootLayer, |
| TransparencyClipBoxBehavior transparencyBehavior, const LayoutSize& subPixelAccumulation) |
| { |
| // If we have a mask, then the clip is limited to the border box area (and there is |
| // no need to examine child layers). |
| if (!layer->renderer()->hasMask()) { |
| // Note: we don't have to walk z-order lists since transparent elements always establish |
| // a stacking container. This means we can just walk the layer tree directly. |
| for (RenderLayer* curr = layer->firstChild(); curr; curr = curr->nextSibling()) |
| clipRect.unite(transparencyClipBox(curr, rootLayer, transparencyBehavior, DescendantsOfTransparencyClipBox, subPixelAccumulation)); |
| } |
| } |
| |
| static LayoutRect transparencyClipBox(const RenderLayer* layer, const RenderLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior, |
| TransparencyClipBoxMode transparencyMode, const LayoutSize& subPixelAccumulation) |
| { |
| // FIXME: Although this function completely ignores CSS-imposed clipping, we did already intersect with the |
| // paintDirtyRect, and that should cut down on the amount we have to paint. Still it |
| // would be better to respect clips. |
| |
| if (rootLayer != layer && ((transparencyBehavior == PaintingTransparencyClipBox && layer->paintsWithTransform()) |
| || (transparencyBehavior == HitTestingTransparencyClipBox && layer->hasTransform()))) { |
| // The best we can do here is to use enclosed bounding boxes to establish a "fuzzy" enough clip to encompass |
| // the transformed layer and all of its children. |
| const RenderLayer* rootLayerForTransform = rootLayer; |
| LayoutPoint delta; |
| layer->convertToLayerCoords(rootLayerForTransform, delta); |
| |
| delta.move(subPixelAccumulation); |
| IntPoint pixelSnappedDelta = roundedIntPoint(delta); |
| TransformationMatrix transform; |
| transform.translate(pixelSnappedDelta.x(), pixelSnappedDelta.y()); |
| transform = transform * *layer->transform(); |
| |
| // We don't use fragment boxes when collecting a transformed layer's bounding box, since it always |
| // paints unfragmented. |
| LayoutRect clipRect = layer->physicalBoundingBox(layer); |
| expandClipRectForDescendantsAndReflection(clipRect, layer, layer, transparencyBehavior, subPixelAccumulation); |
| layer->renderer()->style()->filterOutsets().expandRect(clipRect); |
| LayoutRect result = transform.mapRect(clipRect); |
| return result; |
| } |
| |
| LayoutRect clipRect = layer->physicalBoundingBox(rootLayer); |
| expandClipRectForDescendantsAndReflection(clipRect, layer, rootLayer, transparencyBehavior, subPixelAccumulation); |
| layer->renderer()->style()->filterOutsets().expandRect(clipRect); |
| clipRect.move(subPixelAccumulation); |
| return clipRect; |
| } |
| |
| LayoutRect RenderLayer::paintingExtent(const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, const LayoutSize& subPixelAccumulation) |
| { |
| return intersection(transparencyClipBox(this, rootLayer, PaintingTransparencyClipBox, RootOfTransparencyClipBox, subPixelAccumulation), paintDirtyRect); |
| } |
| |
| void RenderLayer::beginTransparencyLayers(GraphicsContext* context, const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, const LayoutSize& subPixelAccumulation) |
| { |
| if (isTransparent() && m_usedTransparency) |
| return; |
| |
| RenderLayer* ancestor = transparentPaintingAncestor(); |
| if (ancestor) |
| ancestor->beginTransparencyLayers(context, rootLayer, paintDirtyRect, subPixelAccumulation); |
| |
| if (isTransparent()) { |
| m_usedTransparency = true; |
| context->save(); |
| LayoutRect clipRect = paintingExtent(rootLayer, paintDirtyRect, subPixelAccumulation); |
| context->clip(clipRect); |
| |
| context->beginTransparencyLayer(renderer()->opacity()); |
| |
| #ifdef REVEAL_TRANSPARENCY_LAYERS |
| context->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f)); |
| context->fillRect(clipRect); |
| #endif |
| } |
| } |
| |
| void* RenderLayer::operator new(size_t sz) |
| { |
| return partitionAlloc(Partitions::getRenderingPartition(), sz); |
| } |
| |
| void RenderLayer::operator delete(void* ptr) |
| { |
| partitionFree(ptr); |
| } |
| |
| void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild) |
| { |
| RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild(); |
| if (prevSibling) { |
| child->setPreviousSibling(prevSibling); |
| prevSibling->setNextSibling(child); |
| ASSERT(prevSibling != child); |
| } else |
| setFirstChild(child); |
| |
| if (beforeChild) { |
| beforeChild->setPreviousSibling(child); |
| child->setNextSibling(beforeChild); |
| ASSERT(beforeChild != child); |
| } else |
| setLastChild(child); |
| |
| child->m_parent = this; |
| |
| if (child->stackingNode()->isNormalFlowOnly()) |
| m_stackingNode->dirtyNormalFlowList(); |
| |
| if (!child->stackingNode()->isNormalFlowOnly() || child->firstChild()) { |
| // Dirty the z-order list in which we are contained. The ancestorStackingContextNode() can be null in the |
| // case where we're building up generated content layers. This is ok, since the lists will start |
| // off dirty in that case anyway. |
| child->stackingNode()->dirtyStackingContextZOrderLists(); |
| } |
| |
| dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| } |
| |
| RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild) |
| { |
| if (oldChild->previousSibling()) |
| oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); |
| if (oldChild->nextSibling()) |
| oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); |
| |
| if (m_first == oldChild) |
| m_first = oldChild->nextSibling(); |
| if (m_last == oldChild) |
| m_last = oldChild->previousSibling(); |
| |
| if (oldChild->stackingNode()->isNormalFlowOnly()) |
| m_stackingNode->dirtyNormalFlowList(); |
| if (!oldChild->stackingNode()->isNormalFlowOnly() || oldChild->firstChild()) { |
| // Dirty the z-order list in which we are contained. When called via the |
| // reattachment process in removeOnlyThisLayer, the layer may already be disconnected |
| // from the main layer tree, so we need to null-check the |
| // |stackingContext| value. |
| oldChild->stackingNode()->dirtyStackingContextZOrderLists(); |
| } |
| |
| oldChild->setPreviousSibling(0); |
| oldChild->setNextSibling(0); |
| oldChild->m_parent = 0; |
| |
| dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| |
| return oldChild; |
| } |
| |
| void RenderLayer::removeOnlyThisLayer() |
| { |
| if (!m_parent) |
| return; |
| |
| m_clipper.clearClipRectsIncludingDescendants(); |
| |
| RenderLayer* nextSib = nextSibling(); |
| |
| // Now walk our kids and reattach them to our parent. |
| RenderLayer* current = m_first; |
| while (current) { |
| RenderLayer* next = current->nextSibling(); |
| removeChild(current); |
| m_parent->addChild(current, nextSib); |
| |
| // FIXME: We should call a specialized version of this function. |
| current->updateLayerPositionsAfterLayout(); |
| current = next; |
| } |
| |
| // Remove us from the parent. |
| m_parent->removeChild(this); |
| m_renderer->destroyLayer(); |
| } |
| |
| void RenderLayer::insertOnlyThisLayer() |
| { |
| if (!m_parent && renderer()->parent()) { |
| // We need to connect ourselves when our renderer() has a parent. |
| // Find our enclosingLayer and add ourselves. |
| RenderLayer* parentLayer = renderer()->parent()->enclosingLayer(); |
| ASSERT(parentLayer); |
| RenderLayer* beforeChild = renderer()->parent()->findNextLayer(parentLayer, renderer()); |
| parentLayer->addChild(this, beforeChild); |
| } |
| |
| // Remove all descendant layers from the hierarchy and add them to the new position. |
| for (RenderObject* curr = renderer()->slowFirstChild(); curr; curr = curr->nextSibling()) |
| curr->moveLayers(m_parent, this); |
| |
| // Clear out all the clip rects. |
| m_clipper.clearClipRectsIncludingDescendants(); |
| } |
| |
| // Returns the layer reached on the walk up towards the ancestor. |
| static inline const RenderLayer* accumulateOffsetTowardsAncestor(const RenderLayer* layer, const RenderLayer* ancestorLayer, LayoutPoint& location) |
| { |
| ASSERT(ancestorLayer != layer); |
| |
| const RenderLayerModelObject* renderer = layer->renderer(); |
| EPosition position = renderer->style()->position(); |
| |
| RenderLayer* parentLayer; |
| if (position == AbsolutePosition) { |
| // Do what enclosingPositionedAncestor() does, but check for ancestorLayer along the way. |
| parentLayer = layer->parent(); |
| bool foundAncestorFirst = false; |
| while (parentLayer) { |
| // RenderFlowThread is a positioned container, child of RenderView, positioned at (0,0). |
| // This implies that, for out-of-flow positioned elements inside a RenderFlowThread, |
| // we are bailing out before reaching root layer. |
| if (parentLayer->isPositionedContainer()) |
| break; |
| |
| if (parentLayer == ancestorLayer) { |
| foundAncestorFirst = true; |
| break; |
| } |
| |
| parentLayer = parentLayer->parent(); |
| } |
| |
| if (foundAncestorFirst) { |
| // Found ancestorLayer before the abs. positioned container, so compute offset of both relative |
| // to enclosingPositionedAncestor and subtract. |
| RenderLayer* positionedAncestor = parentLayer->enclosingPositionedAncestor(); |
| |
| LayoutPoint thisCoords; |
| layer->convertToLayerCoords(positionedAncestor, thisCoords); |
| |
| LayoutPoint ancestorCoords; |
| ancestorLayer->convertToLayerCoords(positionedAncestor, ancestorCoords); |
| |
| location += (thisCoords - ancestorCoords); |
| return ancestorLayer; |
| } |
| } else |
| parentLayer = layer->parent(); |
| |
| if (!parentLayer) |
| return 0; |
| |
| location += toSize(layer->location()); |
| return parentLayer; |
| } |
| |
| void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutPoint& location) const |
| { |
| if (ancestorLayer == this) |
| return; |
| |
| const RenderLayer* currLayer = this; |
| while (currLayer && currLayer != ancestorLayer) |
| currLayer = accumulateOffsetTowardsAncestor(currLayer, ancestorLayer, location); |
| } |
| |
| void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutRect& rect) const |
| { |
| LayoutPoint delta; |
| convertToLayerCoords(ancestorLayer, delta); |
| rect.move(-delta.x(), -delta.y()); |
| } |
| |
| void RenderLayer::didUpdateNeedsCompositedScrolling() |
| { |
| updateSelfPaintingLayer(); |
| } |
| |
| void RenderLayer::updateStackingNode() |
| { |
| if (requiresStackingNode()) |
| m_stackingNode = adoptPtr(new RenderLayerStackingNode(this)); |
| else |
| m_stackingNode = nullptr; |
| } |
| |
| void RenderLayer::updateScrollableArea() |
| { |
| if (requiresScrollableArea()) |
| m_scrollableArea = adoptPtr(new RenderLayerScrollableArea(*this)); |
| else |
| m_scrollableArea = nullptr; |
| } |
| |
| bool RenderLayer::hasOverflowControls() const |
| { |
| return m_scrollableArea && m_scrollableArea->hasScrollbar(); |
| } |
| |
| void RenderLayer::paint(GraphicsContext* context, const LayoutRect& damageRect, RenderObject* paintingRoot) |
| { |
| TRACE_EVENT0("blink", "RenderLayer::paint"); |
| LayerPaintingInfo paintingInfo(this, enclosingIntRect(damageRect), LayoutSize(), paintingRoot); |
| paintLayer(context, paintingInfo, PaintContent); |
| } |
| |
| void RenderLayer::paintOverlayScrollbars(GraphicsContext* context, const LayoutRect& damageRect, RenderObject* paintingRoot) |
| { |
| if (!m_containsDirtyOverlayScrollbars) |
| return; |
| |
| LayerPaintingInfo paintingInfo(this, enclosingIntRect(damageRect), LayoutSize(), paintingRoot); |
| paintLayer(context, paintingInfo, PaintOverlayScrollbars); |
| |
| m_containsDirtyOverlayScrollbars = false; |
| } |
| |
| static bool inContainingBlockChain(RenderLayer* startLayer, RenderLayer* endLayer) |
| { |
| if (startLayer == endLayer) |
| return true; |
| |
| RenderView* view = startLayer->renderer()->view(); |
| for (RenderBlock* currentBlock = startLayer->renderer()->containingBlock(); currentBlock && currentBlock != view; currentBlock = currentBlock->containingBlock()) { |
| if (currentBlock->layer() == endLayer) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void RenderLayer::clipToRect(const LayerPaintingInfo& localPaintingInfo, GraphicsContext* context, const ClipRect& clipRect, |
| BorderRadiusClippingRule rule) |
| { |
| if (clipRect.rect() == localPaintingInfo.paintDirtyRect && !clipRect.hasRadius()) |
| return; |
| context->save(); |
| context->clip(pixelSnappedIntRect(clipRect.rect())); |
| |
| if (!clipRect.hasRadius()) |
| return; |
| |
| // If the clip rect has been tainted by a border radius, then we have to walk up our layer chain applying the clips from |
| // any layers with overflow. The condition for being able to apply these clips is that the overflow object be in our |
| // containing block chain so we check that also. |
| for (RenderLayer* layer = rule == IncludeSelfForBorderRadius ? this : parent(); layer; layer = layer->parent()) { |
| // Composited scrolling layers handle border-radius clip in the compositor via a mask layer. We do not |
| // want to apply a border-radius clip to the layer contents itself, because that would require re-rastering |
| // every frame to update the clip. We only want to make sure that the mask layer is properly clipped so |
| // that it can in turn clip the scrolled contents in the compositor. |
| if (layer->needsCompositedScrolling()) |
| break; |
| |
| if (layer->renderer()->hasOverflowClip() && layer->renderer()->style()->hasBorderRadius() && inContainingBlockChain(this, layer)) { |
| LayoutPoint delta; |
| layer->convertToLayerCoords(localPaintingInfo.rootLayer, delta); |
| context->clipRoundedRect(layer->renderer()->style()->getRoundedInnerBorderFor(LayoutRect(delta, layer->size()))); |
| } |
| |
| if (layer == localPaintingInfo.rootLayer) |
| break; |
| } |
| } |
| |
| void RenderLayer::restoreClip(GraphicsContext* context, const LayoutRect& paintDirtyRect, const ClipRect& clipRect) |
| { |
| if (clipRect.rect() == paintDirtyRect && !clipRect.hasRadius()) |
| return; |
| context->restore(); |
| } |
| |
| void RenderLayer::paintLayer(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags) |
| { |
| // Non self-painting leaf layers don't need to be painted as their renderer() should properly paint itself. |
| if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant()) |
| return; |
| |
| // If this layer is totally invisible then there is nothing to paint. |
| if (!renderer()->opacity()) |
| return; |
| |
| if (paintsWithTransform()) { |
| TransformationMatrix layerTransform = renderableTransform(); |
| // If the transform can't be inverted, then don't paint anything. |
| if (!layerTransform.isInvertible()) |
| return; |
| |
| // If we have a transparency layer enclosing us and we are the root of a transform, then we need to establish the transparency |
| // layer from the parent now, assuming there is a parent |
| if (isTransparent()) { |
| if (parent()) |
| parent()->beginTransparencyLayers(context, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation); |
| else |
| beginTransparencyLayers(context, paintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation); |
| } |
| |
| // Make sure the parent's clip rects have been calculated. |
| ClipRect clipRect = paintingInfo.paintDirtyRect; |
| if (parent()) { |
| ClipRectsContext clipRectsContext(paintingInfo.rootLayer, PaintingClipRects); |
| clipRect = clipper().backgroundClipRect(clipRectsContext); |
| clipRect.intersect(paintingInfo.paintDirtyRect); |
| |
| // Push the parent coordinate space's clip. |
| parent()->clipToRect(paintingInfo, context, clipRect); |
| } |
| |
| paintLayerByApplyingTransform(context, paintingInfo, paintFlags); |
| |
| // Restore the clip. |
| if (parent()) |
| parent()->restoreClip(context, paintingInfo.paintDirtyRect, clipRect); |
| |
| return; |
| } |
| |
| paintLayerContents(context, paintingInfo, paintFlags); |
| } |
| |
| void RenderLayer::paintLayerContents(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags) |
| { |
| ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant()); |
| |
| float deviceScaleFactor = blink::deviceScaleFactor(renderer()->frame()); |
| context->setDeviceScaleFactor(deviceScaleFactor); |
| |
| GraphicsContext* transparencyLayerContext = context; |
| |
| m_stackingNode->updateLayerListsIfNeeded(); |
| |
| LayoutPoint offsetFromRoot; |
| convertToLayerCoords(paintingInfo.rootLayer, offsetFromRoot); |
| |
| LayoutRect rootRelativeBounds; |
| bool rootRelativeBoundsComputed = false; |
| |
| // Apply clip-path to context. |
| GraphicsContextStateSaver clipStateSaver(*context, false); |
| RenderStyle* style = renderer()->style(); |
| |
| // Clip-path, like border radius, must not be applied to the contents of a composited-scrolling container. |
| // It must, however, still be applied to the mask layer, so that the compositor can properly mask the |
| // scrolling contents and scrollbars. |
| if (renderer()->hasClipPath() && style && !needsCompositedScrolling()) { |
| ASSERT(style->clipPath()); |
| if (style->clipPath()->type() == ClipPathOperation::SHAPE) { |
| ShapeClipPathOperation* clipPath = toShapeClipPathOperation(style->clipPath()); |
| if (clipPath->isValid()) { |
| clipStateSaver.save(); |
| |
| if (!rootRelativeBoundsComputed) { |
| rootRelativeBounds = physicalBoundingBoxIncludingReflectionAndStackingChildren(paintingInfo.rootLayer, offsetFromRoot); |
| rootRelativeBoundsComputed = true; |
| } |
| |
| context->clipPath(clipPath->path(rootRelativeBounds), clipPath->windRule()); |
| } |
| } |
| } |
| |
| LayerPaintingInfo localPaintingInfo(paintingInfo); |
| FilterEffectRendererHelper filterPainter(filterRenderer() && paintsWithFilters()); |
| |
| LayoutRect layerBounds; |
| ClipRect backgroundRect, foregroundRect, outlineRect; |
| ClipRectsContext clipRectsContext(localPaintingInfo.rootLayer, PaintingClipRects, localPaintingInfo.subPixelAccumulation); |
| clipper().calculateRects(clipRectsContext, localPaintingInfo.paintDirtyRect, |
| layerBounds, backgroundRect, foregroundRect, outlineRect, |
| &offsetFromRoot); |
| |
| bool isPaintingOverlayScrollbars = paintFlags == PaintOverlayScrollbars; |
| bool shouldPaintContent = isSelfPaintingLayer() && !isPaintingOverlayScrollbars |
| && intersectsDamageRect(layerBounds, backgroundRect.rect(), localPaintingInfo.rootLayer, &offsetFromRoot); |
| |
| bool haveTransparency = isTransparent(); |
| |
| if (filterPainter.haveFilterEffect()) { |
| ASSERT(this->filterInfo()); |
| |
| if (!rootRelativeBoundsComputed) |
| rootRelativeBounds = physicalBoundingBoxIncludingReflectionAndStackingChildren(paintingInfo.rootLayer, offsetFromRoot); |
| |
| if (filterPainter.prepareFilterEffect(this, rootRelativeBounds, paintingInfo.paintDirtyRect)) { |
| // Rewire the old context to a memory buffer, so that we can capture the contents of the layer. |
| // NOTE: We saved the old context in the "transparencyLayerContext" local variable, to be able to start a transparency layer |
| // on the original context and avoid duplicating "beginFilterEffect" after each transparency layer call. Also, note that |
| // beginTransparencyLayers will only create a single lazy transparency layer, even though it is called twice in this method. |
| // With deferred filters, we don't need a separate context, but we do need to do transparency and clipping before starting |
| // filter processing. |
| // FIXME: when the legacy path is removed, remove the transparencyLayerContext as well. |
| if (haveTransparency) { |
| // If we have a filter and transparency, we have to eagerly start a transparency layer here, rather than risk a child layer lazily starts one after filter processing. |
| beginTransparencyLayers(context, localPaintingInfo.rootLayer, paintingInfo.paintDirtyRect, paintingInfo.subPixelAccumulation); |
| } |
| // We'll handle clipping to the dirty rect before filter rasterization. |
| // Filter processing will automatically expand the clip rect and the offscreen to accommodate any filter outsets. |
| // FIXME: It is incorrect to just clip to the damageRect here once multiple fragments are involved. |
| clipToRect(localPaintingInfo, context, backgroundRect); |
| // Subsequent code should not clip to the dirty rect, since we've already |
| // done it above, and doing it later will defeat the outsets. |
| localPaintingInfo.clipToDirtyRect = false; |
| |
| context = filterPainter.beginFilterEffect(context); |
| } |
| } |
| |
| // If this layer's renderer is a child of the paintingRoot, we render unconditionally, which |
| // is done by passing a nil paintingRoot down to our renderer (as if no paintingRoot was ever set). |
| // Else, our renderer tree may or may not contain the painting root, so we pass that root along |
| // so it will be tested against as we descend through the renderers. |
| RenderObject* paintingRootForRenderer = 0; |
| if (localPaintingInfo.paintingRoot && !renderer()->isDescendantOf(localPaintingInfo.paintingRoot)) |
| paintingRootForRenderer = localPaintingInfo.paintingRoot; |
| |
| LayoutPoint layerLocation = toPoint(layerBounds.location() - renderBoxLocation() + localPaintingInfo.subPixelAccumulation); |
| |
| if (shouldPaintContent) { |
| paintBackground(context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency, |
| localPaintingInfo, paintingRootForRenderer, layerLocation, backgroundRect); |
| } |
| |
| paintChildren(NegativeZOrderChildren, context, paintingInfo, paintFlags); |
| |
| if (shouldPaintContent) { |
| paintForeground(context, transparencyLayerContext, paintingInfo.paintDirtyRect, haveTransparency, |
| localPaintingInfo, paintingRootForRenderer, layerLocation, foregroundRect); |
| } |
| |
| paintOutline(context, localPaintingInfo, paintingRootForRenderer, layerLocation, outlineRect); |
| paintChildren(NormalFlowChildren | PositiveZOrderChildren, context, paintingInfo, paintFlags); |
| |
| if (isPaintingOverlayScrollbars) |
| paintOverflowControls(context, localPaintingInfo, layerLocation, backgroundRect); |
| |
| if (filterPainter.hasStartedFilterEffect()) { |
| context = filterPainter.applyFilterEffect(); |
| restoreClip(transparencyLayerContext, localPaintingInfo.paintDirtyRect, backgroundRect); |
| } |
| |
| // Make sure that we now use the original transparency context. |
| ASSERT(transparencyLayerContext == context); |
| |
| if (shouldPaintContent && renderer()->hasMask()) |
| paintMask(context, localPaintingInfo, paintingRootForRenderer, layerLocation, backgroundRect); |
| |
| // End our transparency layer |
| if (haveTransparency && m_usedTransparency) { |
| context->endLayer(); |
| context->restore(); |
| m_usedTransparency = false; |
| } |
| } |
| |
| void RenderLayer::paintLayerByApplyingTransform(GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags, const LayoutPoint& translationOffset) |
| { |
| // This involves subtracting out the position of the layer in our current coordinate space, but preserving |
| // the accumulated error for sub-pixel layout. |
| LayoutPoint delta; |
| convertToLayerCoords(paintingInfo.rootLayer, delta); |
| delta.moveBy(translationOffset); |
| TransformationMatrix transform(renderableTransform()); |
| IntPoint roundedDelta = roundedIntPoint(delta); |
| transform.translateRight(roundedDelta.x(), roundedDelta.y()); |
| LayoutSize adjustedSubPixelAccumulation = paintingInfo.subPixelAccumulation + (delta - roundedDelta); |
| |
| // Apply the transform. |
| GraphicsContextStateSaver stateSaver(*context, false); |
| if (!transform.isIdentity()) { |
| stateSaver.save(); |
| context->concatCTM(transform.toAffineTransform()); |
| } |
| |
| // Now do a paint with the root layer shifted to be us. |
| LayerPaintingInfo transformedPaintingInfo(this, enclosingIntRect(transform.inverse().mapRect(paintingInfo.paintDirtyRect)), |
| adjustedSubPixelAccumulation, paintingInfo.paintingRoot); |
| paintLayerContents(context, transformedPaintingInfo, paintFlags); |
| } |
| |
| void RenderLayer::paintChildren(unsigned childrenToVisit, GraphicsContext* context, const LayerPaintingInfo& paintingInfo, PaintLayerFlags paintFlags) |
| { |
| if (!hasSelfPaintingLayerDescendant()) |
| return; |
| |
| #if ENABLE(ASSERT) |
| LayerListMutationDetector mutationChecker(m_stackingNode.get()); |
| #endif |
| |
| RenderLayerStackingNodeIterator iterator(*m_stackingNode, childrenToVisit); |
| while (RenderLayerStackingNode* child = iterator.next()) { |
| child->layer()->paintLayer(context, paintingInfo, paintFlags); |
| } |
| } |
| |
| void RenderLayer::paintBackground(GraphicsContext* context, GraphicsContext* transparencyLayerContext, |
| const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, |
| RenderObject* paintingRootForRenderer, LayoutPoint& layerLocation, ClipRect& layerBackgroundRect) |
| { |
| // Begin transparency layers lazily now that we know we have to paint something. |
| if (haveTransparency) |
| beginTransparencyLayers(transparencyLayerContext, localPaintingInfo.rootLayer, transparencyPaintDirtyRect, localPaintingInfo.subPixelAccumulation); |
| |
| if (localPaintingInfo.clipToDirtyRect) { |
| // Paint our background first, before painting any child layers. |
| // Establish the clip used to paint our background. |
| clipToRect(localPaintingInfo, context, layerBackgroundRect, DoNotIncludeSelfForBorderRadius); // Background painting will handle clipping to self. |
| } |
| |
| // Paint the background. |
| // FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info. |
| PaintInfo paintInfo(context, pixelSnappedIntRect(layerBackgroundRect.rect()), PaintPhaseBlockBackground, paintingRootForRenderer, 0, localPaintingInfo.rootLayer->renderer()); |
| renderer()->paint(paintInfo, layerLocation); |
| |
| if (localPaintingInfo.clipToDirtyRect) |
| restoreClip(context, localPaintingInfo.paintDirtyRect, layerBackgroundRect); |
| } |
| |
| void RenderLayer::paintForeground(GraphicsContext* context, GraphicsContext* transparencyLayerContext, |
| const LayoutRect& transparencyPaintDirtyRect, bool haveTransparency, const LayerPaintingInfo& localPaintingInfo, |
| RenderObject* paintingRootForRenderer, LayoutPoint& layerLocation, ClipRect& layerForegroundRect) |
| { |
| bool foregroundRectIsEmpty = layerForegroundRect.isEmpty(); |
| |
| // Begin transparency if we have something to paint. |
| if (haveTransparency && !foregroundRectIsEmpty) |
| beginTransparencyLayers(transparencyLayerContext, localPaintingInfo.rootLayer, transparencyPaintDirtyRect, localPaintingInfo.subPixelAccumulation); |
| |
| // Optimize clipping for the single fragment case. |
| bool shouldClip = localPaintingInfo.clipToDirtyRect && !foregroundRectIsEmpty; |
| if (shouldClip) |
| clipToRect(localPaintingInfo, context, layerForegroundRect); |
| |
| if (!foregroundRectIsEmpty) { |
| // We have to loop through every fragment multiple times, since we have to issue paint invalidations in each specific phase in order for |
| // interleaving of the fragments to work properly. |
| // FIXME(sky): Do we still need this for anything now that we don't have fragments? |
| paintForegroundWithPhase(PaintPhaseChildBlockBackgrounds, |
| context, localPaintingInfo, paintingRootForRenderer, |
| layerLocation, layerForegroundRect); |
| paintForegroundWithPhase(PaintPhaseForeground, |
| context, localPaintingInfo, paintingRootForRenderer, |
| layerLocation, layerForegroundRect); |
| paintForegroundWithPhase(PaintPhaseChildOutlines, |
| context, localPaintingInfo, paintingRootForRenderer, |
| layerLocation, layerForegroundRect); |
| } |
| |
| if (shouldClip) |
| restoreClip(context, localPaintingInfo.paintDirtyRect, layerForegroundRect); |
| } |
| |
| void RenderLayer::paintForegroundWithPhase(PaintPhase phase, GraphicsContext* context, |
| const LayerPaintingInfo& localPaintingInfo, RenderObject* paintingRootForRenderer, LayoutPoint& layerLocation, ClipRect& layerForegroundRect) |
| { |
| PaintInfo paintInfo(context, pixelSnappedIntRect(layerForegroundRect.rect()), phase, paintingRootForRenderer, 0, localPaintingInfo.rootLayer->renderer()); |
| renderer()->paint(paintInfo, layerLocation); |
| } |
| |
| void RenderLayer::paintOutline(GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo, |
| RenderObject* paintingRootForRenderer, LayoutPoint& layerLocation, ClipRect& layerOutlineRect) |
| { |
| if (layerOutlineRect.isEmpty()) |
| return; |
| |
| PaintInfo paintInfo(context, pixelSnappedIntRect(layerOutlineRect.rect()), PaintPhaseSelfOutline, paintingRootForRenderer, 0, localPaintingInfo.rootLayer->renderer()); |
| clipToRect(localPaintingInfo, context, layerOutlineRect, DoNotIncludeSelfForBorderRadius); |
| renderer()->paint(paintInfo, layerLocation); |
| restoreClip(context, localPaintingInfo.paintDirtyRect, layerOutlineRect); |
| } |
| |
| void RenderLayer::paintMask(GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo, |
| RenderObject* paintingRootForRenderer, LayoutPoint& layerLocation, ClipRect& layerBackgroundRect) |
| { |
| if (localPaintingInfo.clipToDirtyRect) |
| clipToRect(localPaintingInfo, context, layerBackgroundRect, DoNotIncludeSelfForBorderRadius); // Mask painting will handle clipping to self. |
| |
| // Paint the mask. |
| // FIXME: Eventually we will collect the region from the fragment itself instead of just from the paint info. |
| PaintInfo paintInfo(context, pixelSnappedIntRect(layerBackgroundRect.rect()), PaintPhaseMask, paintingRootForRenderer, 0, localPaintingInfo.rootLayer->renderer()); |
| renderer()->paint(paintInfo, layerLocation); |
| |
| if (localPaintingInfo.clipToDirtyRect) |
| restoreClip(context, localPaintingInfo.paintDirtyRect, layerBackgroundRect); |
| } |
| |
| void RenderLayer::paintOverflowControls(GraphicsContext* context, const LayerPaintingInfo& localPaintingInfo, LayoutPoint& layerLocation, ClipRect& layerBackgroundRect) |
| { |
| clipToRect(localPaintingInfo, context, layerBackgroundRect); |
| if (RenderLayerScrollableArea* scrollableArea = this->scrollableArea()) |
| scrollableArea->paintOverflowControls(context, roundedIntPoint(layerLocation), pixelSnappedIntRect(layerBackgroundRect.rect()), true); |
| restoreClip(context, localPaintingInfo.paintDirtyRect, layerBackgroundRect); |
| } |
| |
| static inline LayoutRect frameVisibleRect(RenderObject* renderer) |
| { |
| FrameView* frameView = renderer->document().view(); |
| if (!frameView) |
| return LayoutRect(); |
| |
| return frameView->visibleContentRect(); |
| } |
| |
| bool RenderLayer::hitTest(const HitTestRequest& request, HitTestResult& result) |
| { |
| return hitTest(request, result.hitTestLocation(), result); |
| } |
| |
| bool RenderLayer::hitTest(const HitTestRequest& request, const HitTestLocation& hitTestLocation, HitTestResult& result) |
| { |
| ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant()); |
| |
| // RenderView should make sure to update layout before entering hit testing |
| ASSERT(!renderer()->frame()->view()->layoutPending()); |
| ASSERT(!renderer()->document().renderView()->needsLayout()); |
| |
| LayoutRect hitTestArea = renderer()->view()->documentRect(); |
| if (!request.ignoreClipping()) |
| hitTestArea.intersect(frameVisibleRect(renderer())); |
| |
| RenderLayer* insideLayer = hitTestLayer(this, 0, request, result, hitTestArea, hitTestLocation, false); |
| if (!insideLayer) { |
| // We didn't hit any layer. If we are the root layer and the mouse is -- or just was -- down, |
| // return ourselves. We do this so mouse events continue getting delivered after a drag has |
| // exited the WebView, and so hit testing over a scrollbar hits the content document. |
| if ((request.active() || request.release()) && isRootLayer()) { |
| renderer()->updateHitTestResult(result, hitTestLocation.point()); |
| insideLayer = this; |
| } |
| } |
| |
| // Now determine if the result is inside an anchor - if the urlElement isn't already set. |
| Node* node = result.innerNode(); |
| if (node && !result.URLElement()) |
| result.setURLElement(node->enclosingLinkEventParentOrSelf()); |
| |
| // Now return whether we were inside this layer (this will always be true for the root |
| // layer). |
| return insideLayer; |
| } |
| |
| Node* RenderLayer::enclosingElement() const |
| { |
| for (RenderObject* r = renderer(); r; r = r->parent()) { |
| if (Node* e = r->node()) |
| return e; |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| // Compute the z-offset of the point in the transformState. |
| // This is effectively projecting a ray normal to the plane of ancestor, finding where that |
| // ray intersects target, and computing the z delta between those two points. |
| static double computeZOffset(const HitTestingTransformState& transformState) |
| { |
| // We got an affine transform, so no z-offset |
| if (transformState.m_accumulatedTransform.isAffine()) |
| return 0; |
| |
| // Flatten the point into the target plane |
| FloatPoint targetPoint = transformState.mappedPoint(); |
| |
| // Now map the point back through the transform, which computes Z. |
| FloatPoint3D backmappedPoint = transformState.m_accumulatedTransform.mapPoint(FloatPoint3D(targetPoint)); |
| return backmappedPoint.z(); |
| } |
| |
| PassRefPtr<HitTestingTransformState> RenderLayer::createLocalTransformState(RenderLayer* rootLayer, RenderLayer* containerLayer, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, |
| const HitTestingTransformState* containerTransformState, |
| const LayoutPoint& translationOffset) const |
| { |
| RefPtr<HitTestingTransformState> transformState; |
| LayoutPoint offset; |
| if (containerTransformState) { |
| // If we're already computing transform state, then it's relative to the container (which we know is non-null). |
| transformState = HitTestingTransformState::create(*containerTransformState); |
| convertToLayerCoords(containerLayer, offset); |
| } else { |
| // If this is the first time we need to make transform state, then base it off of hitTestLocation, |
| // which is relative to rootLayer. |
| transformState = HitTestingTransformState::create(hitTestLocation.transformedPoint(), hitTestLocation.transformedRect(), FloatQuad(hitTestRect)); |
| convertToLayerCoords(rootLayer, offset); |
| } |
| offset.moveBy(translationOffset); |
| |
| RenderObject* containerRenderer = containerLayer ? containerLayer->renderer() : 0; |
| if (renderer()->shouldUseTransformFromContainer(containerRenderer)) { |
| TransformationMatrix containerTransform; |
| renderer()->getTransformFromContainer(containerRenderer, toLayoutSize(offset), containerTransform); |
| transformState->applyTransform(containerTransform, HitTestingTransformState::AccumulateTransform); |
| } else { |
| transformState->translate(offset.x(), offset.y(), HitTestingTransformState::AccumulateTransform); |
| } |
| |
| return transformState; |
| } |
| |
| |
| static bool isHitCandidate(const RenderLayer* hitLayer, bool canDepthSort, double* zOffset, const HitTestingTransformState* transformState) |
| { |
| if (!hitLayer) |
| return false; |
| |
| // The hit layer is depth-sorting with other layers, so just say that it was hit. |
| if (canDepthSort) |
| return true; |
| |
| // We need to look at z-depth to decide if this layer was hit. |
| if (zOffset) { |
| ASSERT(transformState); |
| // This is actually computing our z, but that's OK because the hitLayer is coplanar with us. |
| double childZOffset = computeZOffset(*transformState); |
| if (childZOffset > *zOffset) { |
| *zOffset = childZOffset; |
| return true; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // hitTestLocation and hitTestRect are relative to rootLayer. |
| // A 'flattening' layer is one preserves3D() == false. |
| // transformState.m_accumulatedTransform holds the transform from the containing flattening layer. |
| // transformState.m_lastPlanarPoint is the hitTestLocation in the plane of the containing flattening layer. |
| // transformState.m_lastPlanarQuad is the hitTestRect as a quad in the plane of the containing flattening layer. |
| // |
| // If zOffset is non-null (which indicates that the caller wants z offset information), |
| // *zOffset on return is the z offset of the hit point relative to the containing flattening layer. |
| RenderLayer* RenderLayer::hitTestLayer(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, bool appliedTransform, |
| const HitTestingTransformState* transformState, double* zOffset) |
| { |
| if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant()) |
| return 0; |
| |
| // The natural thing would be to keep HitTestingTransformState on the stack, but it's big, so we heap-allocate. |
| |
| // Apply a transform if we have one. |
| if (transform() && !appliedTransform) { |
| // Make sure the parent's clip rects have been calculated. |
| if (parent()) { |
| ClipRect clipRect = clipper().backgroundClipRect(ClipRectsContext(rootLayer, RootRelativeClipRects)); |
| // Go ahead and test the enclosing clip now. |
| if (!clipRect.intersects(hitTestLocation)) |
| return 0; |
| } |
| |
| return hitTestLayerByApplyingTransform(rootLayer, containerLayer, request, result, hitTestRect, hitTestLocation, transformState, zOffset); |
| } |
| |
| // Ensure our lists and 3d status are up-to-date. |
| m_stackingNode->updateLayerListsIfNeeded(); |
| update3DTransformedDescendantStatus(); |
| |
| RefPtr<HitTestingTransformState> localTransformState; |
| if (appliedTransform) { |
| // We computed the correct state in the caller (above code), so just reference it. |
| ASSERT(transformState); |
| localTransformState = const_cast<HitTestingTransformState*>(transformState); |
| } else if (transformState || m_has3DTransformedDescendant || preserves3D()) { |
| // We need transform state for the first time, or to offset the container state, so create it here. |
| localTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestLocation, transformState); |
| } |
| |
| // Check for hit test on backface if backface-visibility is 'hidden' |
| if (localTransformState && renderer()->style()->backfaceVisibility() == BackfaceVisibilityHidden) { |
| TransformationMatrix invertedMatrix = localTransformState->m_accumulatedTransform.inverse(); |
| // If the z-vector of the matrix is negative, the back is facing towards the viewer. |
| if (invertedMatrix.m33() < 0) |
| return 0; |
| } |
| |
| RefPtr<HitTestingTransformState> unflattenedTransformState = localTransformState; |
| if (localTransformState && !preserves3D()) { |
| // Keep a copy of the pre-flattening state, for computing z-offsets for the container |
| unflattenedTransformState = HitTestingTransformState::create(*localTransformState); |
| // This layer is flattening, so flatten the state passed to descendants. |
| localTransformState->flatten(); |
| } |
| |
| // The following are used for keeping track of the z-depth of the hit point of 3d-transformed |
| // descendants. |
| double localZOffset = -std::numeric_limits<double>::infinity(); |
| double* zOffsetForDescendantsPtr = 0; |
| double* zOffsetForContentsPtr = 0; |
| |
| bool depthSortDescendants = false; |
| if (preserves3D()) { |
| depthSortDescendants = true; |
| // Our layers can depth-test with our container, so share the z depth pointer with the container, if it passed one down. |
| zOffsetForDescendantsPtr = zOffset ? zOffset : &localZOffset; |
| zOffsetForContentsPtr = zOffset ? zOffset : &localZOffset; |
| } else if (zOffset) { |
| zOffsetForDescendantsPtr = 0; |
| // Container needs us to give back a z offset for the hit layer. |
| zOffsetForContentsPtr = zOffset; |
| } |
| |
| // This variable tracks which layer the mouse ends up being inside. |
| RenderLayer* candidateLayer = 0; |
| |
| // Begin by walking our list of positive layers from highest z-index down to the lowest z-index. |
| RenderLayer* hitLayer = hitTestChildren(PositiveZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation, |
| localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); |
| if (hitLayer) { |
| if (!depthSortDescendants) |
| return hitLayer; |
| candidateLayer = hitLayer; |
| } |
| |
| // Now check our overflow objects. |
| hitLayer = hitTestChildren(NormalFlowChildren, rootLayer, request, result, hitTestRect, hitTestLocation, |
| localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); |
| if (hitLayer) { |
| if (!depthSortDescendants) |
| return hitLayer; |
| candidateLayer = hitLayer; |
| } |
| |
| LayoutRect layerBounds; |
| ClipRect backgroundRect, foregroundRect, outlineRect; |
| ClipRectsContext clipRectsContext(rootLayer, RootRelativeClipRects); |
| clipper().calculateRects(clipRectsContext, hitTestRect, layerBounds, backgroundRect, foregroundRect, outlineRect); |
| |
| // Next we want to see if the mouse pos is inside the child RenderObjects of the layer. |
| if (isSelfPaintingLayer() && foregroundRect.intersects(hitTestLocation)) { |
| // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost. |
| HitTestResult tempResult(result.hitTestLocation()); |
| if (hitTestContents(request, tempResult, layerBounds, hitTestLocation, HitTestDescendants) |
| && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { |
| if (result.isRectBasedTest()) |
| result.append(tempResult); |
| else |
| result = tempResult; |
| if (!depthSortDescendants) |
| return this; |
| // Foreground can depth-sort with descendant layers, so keep this as a candidate. |
| candidateLayer = this; |
| } else if (result.isRectBasedTest()) { |
| result.append(tempResult); |
| } |
| } |
| |
| // Now check our negative z-index children. |
| hitLayer = hitTestChildren(NegativeZOrderChildren, rootLayer, request, result, hitTestRect, hitTestLocation, |
| localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); |
| if (hitLayer) { |
| if (!depthSortDescendants) |
| return hitLayer; |
| candidateLayer = hitLayer; |
| } |
| |
| // If we found a layer, return. Child layers, and foreground always render in front of background. |
| if (candidateLayer) |
| return candidateLayer; |
| |
| if (isSelfPaintingLayer() && backgroundRect.intersects(hitTestLocation)) { |
| HitTestResult tempResult(result.hitTestLocation()); |
| if (hitTestContents(request, tempResult, layerBounds, hitTestLocation, HitTestSelf) |
| && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { |
| if (result.isRectBasedTest()) |
| result.append(tempResult); |
| else |
| result = tempResult; |
| return this; |
| } |
| if (result.isRectBasedTest()) |
| result.append(tempResult); |
| } |
| |
| return 0; |
| } |
| |
| RenderLayer* RenderLayer::hitTestLayerByApplyingTransform(RenderLayer* rootLayer, RenderLayer* containerLayer, const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, const HitTestingTransformState* transformState, double* zOffset, |
| const LayoutPoint& translationOffset) |
| { |
| // Create a transform state to accumulate this transform. |
| RefPtr<HitTestingTransformState> newTransformState = createLocalTransformState(rootLayer, containerLayer, hitTestRect, hitTestLocation, transformState, translationOffset); |
| |
| // If the transform can't be inverted, then don't hit test this layer at all. |
| if (!newTransformState->m_accumulatedTransform.isInvertible()) |
| return 0; |
| |
| // Compute the point and the hit test rect in the coords of this layer by using the values |
| // from the transformState, which store the point and quad in the coords of the last flattened |
| // layer, and the accumulated transform which lets up map through preserve-3d layers. |
| // |
| // We can't just map hitTestLocation and hitTestRect because they may have been flattened (losing z) |
| // by our container. |
| FloatPoint localPoint = newTransformState->mappedPoint(); |
| FloatQuad localPointQuad = newTransformState->mappedQuad(); |
| LayoutRect localHitTestRect = newTransformState->boundsOfMappedArea(); |
| HitTestLocation newHitTestLocation; |
| if (hitTestLocation.isRectBasedTest()) |
| newHitTestLocation = HitTestLocation(localPoint, localPointQuad); |
| else |
| newHitTestLocation = HitTestLocation(localPoint); |
| |
| // Now do a hit test with the root layer shifted to be us. |
| return hitTestLayer(this, containerLayer, request, result, localHitTestRect, newHitTestLocation, true, newTransformState.get(), zOffset); |
| } |
| |
| bool RenderLayer::hitTestContents(const HitTestRequest& request, HitTestResult& result, const LayoutRect& layerBounds, const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter) const |
| { |
| ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant()); |
| |
| if (!renderer()->hitTest(request, result, hitTestLocation, toLayoutPoint(layerBounds.location() - renderBoxLocation()), hitTestFilter)) { |
| // It's wrong to set innerNode, but then claim that you didn't hit anything, unless it is |
| // a rect-based test. |
| ASSERT(!result.innerNode() || (result.isRectBasedTest() && result.rectBasedTestResult().size())); |
| return false; |
| } |
| |
| // For positioned generated content, we might still not have a |
| // node by the time we get to the layer level, since none of |
| // the content in the layer has an element. So just walk up |
| // the tree. |
| if (!result.innerNode() || !result.innerNonSharedNode()) { |
| Node* e = enclosingElement(); |
| if (!result.innerNode()) |
| result.setInnerNode(e); |
| if (!result.innerNonSharedNode()) |
| result.setInnerNonSharedNode(e); |
| } |
| |
| return true; |
| } |
| |
| RenderLayer* RenderLayer::hitTestChildren(ChildrenIteration childrentoVisit, RenderLayer* rootLayer, |
| const HitTestRequest& request, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, |
| const HitTestingTransformState* transformState, |
| double* zOffsetForDescendants, double* zOffset, |
| const HitTestingTransformState* unflattenedTransformState, |
| bool depthSortDescendants) |
| { |
| if (!hasSelfPaintingLayerDescendant()) |
| return 0; |
| |
| RenderLayer* resultLayer = 0; |
| RenderLayerStackingNodeReverseIterator iterator(*m_stackingNode, childrentoVisit); |
| while (RenderLayerStackingNode* child = iterator.next()) { |
| RenderLayer* childLayer = child->layer(); |
| RenderLayer* hitLayer = 0; |
| HitTestResult tempResult(result.hitTestLocation()); |
| hitLayer = childLayer->hitTestLayer(rootLayer, this, request, tempResult, hitTestRect, hitTestLocation, false, transformState, zOffsetForDescendants); |
| |
| // If it a rect-based test, we can safely append the temporary result since it might had hit |
| // nodes but not necesserily had hitLayer set. |
| if (result.isRectBasedTest()) |
| result.append(tempResult); |
| |
| if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) { |
| resultLayer = hitLayer; |
| if (!result.isRectBasedTest()) |
| result = tempResult; |
| if (!depthSortDescendants) |
| break; |
| } |
| } |
| |
| return resultLayer; |
| } |
| |
| bool RenderLayer::intersectsDamageRect(const LayoutRect& layerBounds, const LayoutRect& damageRect, const RenderLayer* rootLayer, const LayoutPoint* offsetFromRoot) const |
| { |
| // Always examine the canvas and the root. |
| // FIXME: Could eliminate the isDocumentElement() check if we fix background painting so that the RenderView |
| // paints the root's background. |
| if (isRootLayer() || renderer()->isDocumentElement()) |
| return true; |
| |
| // If we aren't an inline flow, and our layer bounds do intersect the damage rect, then we |
| // can go ahead and return true. |
| RenderView* view = renderer()->view(); |
| ASSERT(view); |
| if (view && !renderer()->isRenderInline()) { |
| if (layerBounds.intersects(damageRect)) |
| return true; |
| } |
| |
| // Otherwise we need to compute the bounding box of this single layer and see if it intersects |
| // the damage rect. |
| return physicalBoundingBox(rootLayer, offsetFromRoot).intersects(damageRect); |
| } |
| |
| LayoutRect RenderLayer::logicalBoundingBox() const |
| { |
| // There are three special cases we need to consider. |
| // (1) Inline Flows. For inline flows we will create a bounding box that fully encompasses all of the lines occupied by the |
| // inline. In other words, if some <span> wraps to three lines, we'll create a bounding box that fully encloses the |
| // line boxes of all three lines (including overflow on those lines). |
| // (2) Left/Top Overflow. The width/height of layers already includes right/bottom overflow. However, in the case of left/top |
| // overflow, we have to create a bounding box that will extend to include this overflow. |
| // (3) Floats. When a layer has overhanging floats that it paints, we need to make sure to include these overhanging floats |
| // as part of our bounding box. We do this because we are the responsible layer for both hit testing and painting those |
| // floats. |
| LayoutRect result; |
| if (renderer()->isInline() && renderer()->isRenderInline()) { |
| result = toRenderInline(renderer())->linesVisualOverflowBoundingBox(); |
| } else { |
| RenderBox* box = renderBox(); |
| ASSERT(box); |
| result = box->borderBoxRect(); |
| result.unite(box->visualOverflowRect()); |
| } |
| |
| ASSERT(renderer()->view()); |
| return result; |
| } |
| |
| LayoutRect RenderLayer::physicalBoundingBox(const RenderLayer* ancestorLayer, const LayoutPoint* offsetFromRoot) const |
| { |
| LayoutPoint delta; |
| if (offsetFromRoot) |
| delta = *offsetFromRoot; |
| else |
| convertToLayerCoords(ancestorLayer, delta); |
| |
| LayoutRect result = logicalBoundingBox(); |
| result.moveBy(delta); |
| return result; |
| } |
| |
| static void expandRectForReflectionAndStackingChildren(const RenderLayer* ancestorLayer, RenderLayer::CalculateBoundsOptions options, LayoutRect& result) |
| { |
| ASSERT(ancestorLayer->stackingNode()->isStackingContext() || !ancestorLayer->stackingNode()->hasPositiveZOrderList()); |
| |
| #if ENABLE(ASSERT) |
| LayerListMutationDetector mutationChecker(const_cast<RenderLayer*>(ancestorLayer)->stackingNode()); |
| #endif |
| |
| RenderLayerStackingNodeIterator iterator(*ancestorLayer->stackingNode(), AllChildren); |
| while (RenderLayerStackingNode* node = iterator.next()) { |
| result.unite(node->layer()->boundingBoxForCompositing(ancestorLayer, options)); |
| } |
| } |
| |
| LayoutRect RenderLayer::physicalBoundingBoxIncludingReflectionAndStackingChildren(const RenderLayer* ancestorLayer, const LayoutPoint& offsetFromRoot) const |
| { |
| LayoutPoint origin; |
| LayoutRect result = physicalBoundingBox(ancestorLayer, &origin); |
| |
| const_cast<RenderLayer*>(this)->stackingNode()->updateLayerListsIfNeeded(); |
| |
| expandRectForReflectionAndStackingChildren(this, DoNotApplyBoundsChickenEggHacks, result); |
| |
| result.moveBy(offsetFromRoot); |
| return result; |
| } |
| |
| LayoutRect RenderLayer::boundingBoxForCompositing(const RenderLayer* ancestorLayer, CalculateBoundsOptions options) const |
| { |
| if (!isSelfPaintingLayer()) |
| return LayoutRect(); |
| |
| if (!ancestorLayer) |
| ancestorLayer = this; |
| |
| // The root layer is always just the size of the document. |
| if (isRootLayer()) |
| return m_renderer->view()->unscaledDocumentRect(); |
| |
| const bool shouldIncludeTransform = paintsWithTransform() || (options == ApplyBoundsChickenEggHacks && transform()); |
| |
| LayoutRect localClipRect = clipper().localClipRect(); |
| if (localClipRect != PaintInfo::infiniteRect()) { |
| if (shouldIncludeTransform) |
| localClipRect = transform()->mapRect(localClipRect); |
| |
| LayoutPoint delta; |
| convertToLayerCoords(ancestorLayer, delta); |
| localClipRect.moveBy(delta); |
| return localClipRect; |
| } |
| |
| LayoutPoint origin; |
| LayoutRect result = physicalBoundingBox(ancestorLayer, &origin); |
| |
| const_cast<RenderLayer*>(this)->stackingNode()->updateLayerListsIfNeeded(); |
| |
| expandRectForReflectionAndStackingChildren(this, options, result); |
| |
| // FIXME: We can optimize the size of the composited layers, by not enlarging |
| // filtered areas with the outsets if we know that the filter is going to render in hardware. |
| // https://bugs.webkit.org/show_bug.cgi?id=81239 |
| m_renderer->style()->filterOutsets().expandRect(result); |
| |
| if (shouldIncludeTransform) |
| result = transform()->mapRect(result); |
| |
| LayoutPoint delta; |
| convertToLayerCoords(ancestorLayer, delta); |
| result.moveBy(delta); |
| return result; |
| } |
| |
| bool RenderLayer::paintsWithTransform() const |
| { |
| // FIXME(sky): Remove |
| return transform(); |
| } |
| |
| bool RenderLayer::shouldBeSelfPaintingLayer() const |
| { |
| return m_layerType == NormalLayer |
| || (m_scrollableArea && m_scrollableArea->hasOverlayScrollbars()) |
| || needsCompositedScrolling(); |
| } |
| |
| void RenderLayer::updateSelfPaintingLayer() |
| { |
| bool isSelfPaintingLayer = shouldBeSelfPaintingLayer(); |
| if (this->isSelfPaintingLayer() == isSelfPaintingLayer) |
| return; |
| |
| m_isSelfPaintingLayer = isSelfPaintingLayer; |
| |
| if (parent()) |
| parent()->dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| } |
| |
| bool RenderLayer::hasNonEmptyChildRenderers() const |
| { |
| // Some HTML can cause whitespace text nodes to have renderers, like: |
| // <div> |
| // <img src=...> |
| // </div> |
| // so test for 0x0 RenderTexts here |
| for (RenderObject* child = renderer()->slowFirstChild(); child; child = child->nextSibling()) { |
| if (!child->hasLayer()) { |
| if (child->isRenderInline() || !child->isBox()) |
| return true; |
| |
| if (toRenderBox(child)->width() > 0 || toRenderBox(child)->height() > 0) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool RenderLayer::hasBoxDecorationsOrBackground() const |
| { |
| return renderer()->style()->hasBoxDecorations() || renderer()->style()->hasBackground(); |
| } |
| |
| bool RenderLayer::hasVisibleBoxDecorations() const |
| { |
| return hasBoxDecorationsOrBackground() || hasOverflowControls(); |
| } |
| |
| bool RenderLayer::isVisuallyNonEmpty() const |
| { |
| if (hasNonEmptyChildRenderers()) |
| return true; |
| |
| if (renderer()->isReplaced() || renderer()->hasMask()) |
| return true; |
| |
| if (hasVisibleBoxDecorations()) |
| return true; |
| |
| return false; |
| } |
| |
| void RenderLayer::updateFilters(const RenderStyle* oldStyle, const RenderStyle* newStyle) |
| { |
| if (!newStyle->hasFilter() && (!oldStyle || !oldStyle->hasFilter())) |
| return; |
| |
| updateOrRemoveFilterClients(); |
| updateOrRemoveFilterEffectRenderer(); |
| } |
| |
| void RenderLayer::styleChanged(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| m_stackingNode->updateIsNormalFlowOnly(); |
| m_stackingNode->updateStackingNodesAfterStyleChange(oldStyle); |
| |
| if (m_scrollableArea) |
| m_scrollableArea->updateAfterStyleChange(oldStyle); |
| |
| // Overlay scrollbars can make this layer self-painting so we need |
| // to recompute the bit once scrollbars have been updated. |
| updateSelfPaintingLayer(); |
| |
| updateTransform(oldStyle, renderer()->style()); |
| updateFilters(oldStyle, renderer()->style()); |
| } |
| |
| bool RenderLayer::scrollsOverflow() const |
| { |
| if (RenderLayerScrollableArea* scrollableArea = this->scrollableArea()) |
| return scrollableArea->scrollsOverflow(); |
| |
| return false; |
| } |
| |
| FilterOperations RenderLayer::computeFilterOperations(const RenderStyle* style) |
| { |
| return style->filter(); |
| } |
| |
| void RenderLayer::updateOrRemoveFilterClients() |
| { |
| if (!hasFilter()) { |
| removeFilterInfoIfNeeded(); |
| return; |
| } |
| |
| if (renderer()->style()->filter().hasReferenceFilter()) |
| ensureFilterInfo()->updateReferenceFilterClients(renderer()->style()->filter()); |
| else if (hasFilterInfo()) |
| filterInfo()->removeReferenceFilterClients(); |
| } |
| |
| void RenderLayer::updateOrRemoveFilterEffectRenderer() |
| { |
| // FilterEffectRenderer is only used to render the filters in software mode, |
| // so we always need to run updateOrRemoveFilterEffectRenderer after the composited |
| // mode might have changed for this layer. |
| if (!paintsWithFilters()) { |
| // Don't delete the whole filter info here, because we might use it |
| // for loading CSS shader files. |
| if (RenderLayerFilterInfo* filterInfo = this->filterInfo()) |
| filterInfo->setRenderer(nullptr); |
| |
| return; |
| } |
| |
| RenderLayerFilterInfo* filterInfo = ensureFilterInfo(); |
| if (!filterInfo->renderer()) { |
| RefPtr<FilterEffectRenderer> filterRenderer = FilterEffectRenderer::create(); |
| filterInfo->setRenderer(filterRenderer.release()); |
| |
| // We can optimize away code paths in other places if we know that there are no software filters. |
| renderer()->document().view()->setHasSoftwareFilters(true); |
| } |
| |
| // If the filter fails to build, remove it from the layer. It will still attempt to |
| // go through regular processing (e.g. compositing), but never apply anything. |
| if (!filterInfo->renderer()->build(renderer(), computeFilterOperations(renderer()->style()))) |
| filterInfo->setRenderer(nullptr); |
| } |
| |
| } // namespace blink |
| |
| #ifndef NDEBUG |
| void showLayerTree(const blink::RenderLayer* layer) |
| { |
| if (!layer) |
| return; |
| |
| if (blink::LocalFrame* frame = layer->renderer()->frame()) { |
| WTF::String output = externalRepresentation(frame, blink::RenderAsTextShowAllLayers | blink::RenderAsTextShowLayerNesting | blink::RenderAsTextShowCompositedLayers | blink::RenderAsTextShowAddresses | blink::RenderAsTextShowIDAndClass | blink::RenderAsTextDontUpdateLayout | blink::RenderAsTextShowLayoutState); |
| fprintf(stderr, "%s\n", output.utf8().data()); |
| } |
| } |
| |
| void showLayerTree(const blink::RenderObject* renderer) |
| { |
| if (!renderer) |
| return; |
| showLayerTree(renderer->enclosingLayer()); |
| } |
| #endif |