| /* |
| * 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 "config.h" |
| #include "core/paint/PaintLayer.h" |
| |
| #include "core/CSSPropertyNames.h" |
| #include "core/HTMLNames.h" |
| #include "core/css/PseudoStyleRequest.h" |
| #include "core/dom/Document.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/frame/DeprecatedScheduleStyleRecalcDuringLayout.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/html/HTMLFrameElement.h" |
| #include "core/layout/HitTestRequest.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/HitTestingTransformState.h" |
| #include "core/layout/LayoutFlowThread.h" |
| #include "core/layout/LayoutGeometryMap.h" |
| #include "core/layout/LayoutInline.h" |
| #include "core/layout/LayoutPart.h" |
| #include "core/layout/LayoutReplica.h" |
| #include "core/layout/LayoutScrollbar.h" |
| #include "core/layout/LayoutScrollbarPart.h" |
| #include "core/layout/LayoutTreeAsText.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/compositing/CompositedLayerMapping.h" |
| #include "core/layout/compositing/PaintLayerCompositor.h" |
| #include "core/layout/svg/LayoutSVGRoot.h" |
| #include "core/layout/svg/ReferenceFilterBuilder.h" |
| #include "core/page/Page.h" |
| #include "core/page/scrolling/ScrollingCoordinator.h" |
| #include "core/paint/FilterEffectBuilder.h" |
| #include "core/paint/PaintTiming.h" |
| #include "platform/LengthFunctions.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/TraceEvent.h" |
| #include "platform/geometry/FloatPoint3D.h" |
| #include "platform/geometry/FloatRect.h" |
| #include "platform/geometry/TransformState.h" |
| #include "platform/graphics/filters/Filter.h" |
| #include "platform/graphics/filters/SourceGraphic.h" |
| #include "platform/transforms/ScaleTransformOperation.h" |
| #include "platform/transforms/TransformationMatrix.h" |
| #include "platform/transforms/TranslateTransformOperation.h" |
| #include "public/platform/Platform.h" |
| #include "wtf/Partitions.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/text/CString.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| static CompositingQueryMode gCompositingQueryMode = |
| CompositingQueriesAreOnlyAllowedInCertainDocumentLifecyclePhases; |
| |
| } // namespace |
| |
| using namespace HTMLNames; |
| |
| PaintLayer::PaintLayer(LayoutBoxModelObject* layoutObject, PaintLayerType type) |
| : m_layerType(type) |
| , m_hasSelfPaintingLayerDescendant(false) |
| , m_hasSelfPaintingLayerDescendantDirty(false) |
| , m_isRootLayer(layoutObject->isLayoutView()) |
| , m_visibleContentStatusDirty(true) |
| , m_hasVisibleContent(false) |
| , m_visibleDescendantStatusDirty(false) |
| , m_hasVisibleDescendant(false) |
| , m_hasVisibleNonLayerContent(false) |
| #if ENABLE(ASSERT) |
| , m_needsPositionUpdate(true) |
| #endif |
| , 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_hasNonCompositedChild(false) |
| , m_shouldIsolateCompositedDescendants(false) |
| , m_lostGroupedMapping(false) |
| , m_needsRepaint(false) |
| , m_layoutObject(layoutObject) |
| , m_parent(0) |
| , m_previous(0) |
| , m_next(0) |
| , m_first(0) |
| , m_last(0) |
| , m_staticInlinePosition(0) |
| , m_staticBlockPosition(0) |
| , m_enclosingPaginationLayer(0) |
| , m_potentialCompositingReasonsFromStyle(CompositingReasonNone) |
| , m_compositingReasons(CompositingReasonNone) |
| , m_groupedMapping(0) |
| , m_clipper(*layoutObject) |
| { |
| updateStackingNode(); |
| |
| m_isSelfPaintingLayer = shouldBeSelfPaintingLayer(); |
| |
| if (!layoutObject->slowFirstChild() && layoutObject->style()) { |
| m_visibleContentStatusDirty = false; |
| m_hasVisibleContent = layoutObject->style()->visibility() == VISIBLE; |
| } |
| |
| updateScrollableArea(); |
| } |
| |
| PaintLayer::~PaintLayer() |
| { |
| if (layoutObject()->frame() && layoutObject()->frame()->page()) { |
| if (ScrollingCoordinator* scrollingCoordinator = layoutObject()->frame()->page()->scrollingCoordinator()) |
| scrollingCoordinator->willDestroyLayer(this); |
| } |
| |
| removeFilterInfoIfNeeded(); |
| |
| if (groupedMapping()) { |
| DisableCompositingQueryAsserts disabler; |
| setGroupedMapping(0, InvalidateLayerAndRemoveFromMapping); |
| } |
| |
| // Child layers will be deleted by their corresponding layout objects, so |
| // we don't need to delete them ourselves. |
| |
| clearCompositedLayerMapping(true); |
| |
| if (m_reflectionInfo) |
| m_reflectionInfo->destroy(); |
| |
| if (m_scrollableArea) |
| m_scrollableArea->dispose(); |
| } |
| |
| String PaintLayer::debugName() const |
| { |
| if (isReflection()) |
| return layoutObject()->parent()->debugName() + " (reflection)"; |
| return layoutObject()->debugName(); |
| } |
| |
| PaintLayerCompositor* PaintLayer::compositor() const |
| { |
| if (!layoutObject()->view()) |
| return 0; |
| return layoutObject()->view()->compositor(); |
| } |
| |
| void PaintLayer::contentChanged(ContentChangeType changeType) |
| { |
| // updateLayerCompositingState will query compositingReasons for accelerated overflow scrolling. |
| // This is tripped by LayoutTests/compositing/content-changed-chicken-egg.html |
| DisableCompositingQueryAsserts disabler; |
| |
| if (changeType == CanvasChanged) |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateAfterCompositingInputChange); |
| |
| if (changeType == CanvasContextChanged) { |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateAfterCompositingInputChange); |
| |
| // Although we're missing test coverage, we need to call |
| // GraphicsLayer::setContentsToPlatformLayer with the new platform |
| // layer for this canvas. |
| // See http://crbug.com/349195 |
| if (hasCompositedLayerMapping()) |
| compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); |
| } |
| |
| if (m_compositedLayerMapping) |
| m_compositedLayerMapping->contentChanged(changeType); |
| } |
| |
| bool PaintLayer::paintsWithFilters() const |
| { |
| if (!layoutObject()->hasFilter()) |
| return false; |
| |
| // https://code.google.com/p/chromium/issues/detail?id=343759 |
| DisableCompositingQueryAsserts disabler; |
| return !m_compositedLayerMapping || compositingState() != PaintsIntoOwnBacking; |
| } |
| |
| bool PaintLayer::paintsWithBackdropFilters() const |
| { |
| if (!layoutObject()->hasBackdropFilter()) |
| return false; |
| |
| // https://code.google.com/p/chromium/issues/detail?id=343759 |
| DisableCompositingQueryAsserts disabler; |
| return !m_compositedLayerMapping || compositingState() != PaintsIntoOwnBacking; |
| } |
| |
| LayoutSize PaintLayer::subpixelAccumulation() const |
| { |
| return m_subpixelAccumulation; |
| } |
| |
| void PaintLayer::setSubpixelAccumulation(const LayoutSize& size) |
| { |
| m_subpixelAccumulation = size; |
| } |
| |
| void PaintLayer::updateLayerPositionsAfterLayout() |
| { |
| TRACE_EVENT0("blink,benchmark", "PaintLayer::updateLayerPositionsAfterLayout"); |
| |
| m_clipper.clearClipRectsIncludingDescendants(); |
| updateLayerPositionRecursive(); |
| |
| { |
| // FIXME: Remove incremental compositing updates after fixing the chicken/egg issues |
| // https://code.google.com/p/chromium/issues/detail?id=343756 |
| DisableCompositingQueryAsserts disabler; |
| updatePaginationRecursive(enclosingPaginationLayer()); |
| } |
| } |
| |
| void PaintLayer::updateLayerPositionRecursive() |
| { |
| updateLayerPosition(); |
| |
| if (m_reflectionInfo) |
| m_reflectionInfo->reflection()->layout(); |
| |
| // FIXME(400589): We would like to do this in PaintLayerScrollableArea::updateAfterLayout, |
| // but it depends on the size computed by updateLayerPosition. |
| if (m_scrollableArea) { |
| if (ScrollAnimatorBase* scrollAnimator = m_scrollableArea->existingScrollAnimator()) |
| scrollAnimator->updateAfterLayout(); |
| } |
| |
| // FIXME: We should be able to remove this call because we don't care about |
| // any descendant-dependent flags, but code somewhere else is reading these |
| // flags and depending on us to update them. |
| updateDescendantDependentFlags(); |
| |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->updateLayerPositionRecursive(); |
| } |
| |
| void PaintLayer::updateHasSelfPaintingLayerDescendant() const |
| { |
| ASSERT(m_hasSelfPaintingLayerDescendantDirty); |
| |
| m_hasSelfPaintingLayerDescendant = false; |
| |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { |
| if (child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant()) { |
| m_hasSelfPaintingLayerDescendant = true; |
| break; |
| } |
| } |
| |
| m_hasSelfPaintingLayerDescendantDirty = false; |
| } |
| |
| void PaintLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus() |
| { |
| for (PaintLayer* 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 PaintLayer::scrollsWithViewport() const |
| { |
| return layoutObject()->style()->position() == FixedPosition && layoutObject()->containerForFixedPosition() == layoutObject()->view(); |
| } |
| |
| bool PaintLayer::scrollsWithRespectTo(const PaintLayer* other) const |
| { |
| if (scrollsWithViewport() != other->scrollsWithViewport()) |
| return true; |
| return ancestorScrollingLayer() != other->ancestorScrollingLayer(); |
| } |
| |
| void PaintLayer::updateLayerPositionsAfterOverflowScroll(const DoubleSize& scrollDelta) |
| { |
| m_clipper.clearClipRectsIncludingDescendants(); |
| updateLayerPositionsAfterScrollRecursive(scrollDelta, isPaintInvalidationContainer()); |
| } |
| |
| void PaintLayer::updateLayerPositionsAfterScrollRecursive(const DoubleSize& scrollDelta, bool paintInvalidationContainerWasScrolled) |
| { |
| updateLayerPosition(); |
| if (paintInvalidationContainerWasScrolled && !isPaintInvalidationContainer()) { |
| // Paint invalidation rects are in the coordinate space of the paint invalidation container. |
| // If it has scrolled, the rect must be adjusted. Note that it is not safe to reset it to |
| // the current bounds rect, as the LayoutObject may have moved since the last invalidation. |
| // FIXME(416535): Ideally, pending invalidations of scrolling content should be stored in |
| // the coordinate space of the scrolling content layer, so that they need no adjustment. |
| m_layoutObject->adjustPreviousPaintInvalidationForScrollIfNeeded(scrollDelta); |
| } |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { |
| child->updateLayerPositionsAfterScrollRecursive(scrollDelta, |
| paintInvalidationContainerWasScrolled && !child->isPaintInvalidationContainer()); |
| } |
| } |
| |
| void PaintLayer::updateTransformationMatrix() |
| { |
| if (m_transform) { |
| LayoutBox* box = layoutBox(); |
| ASSERT(box); |
| m_transform->makeIdentity(); |
| box->style()->applyTransform(*m_transform, LayoutSize(box->pixelSnappedSize()), ComputedStyle::IncludeTransformOrigin, ComputedStyle::IncludeMotionPath, ComputedStyle::IncludeIndependentTransformProperties); |
| makeMatrixRenderable(*m_transform, compositor()->hasAcceleratedCompositing()); |
| } |
| } |
| |
| void PaintLayer::updateTransform(const ComputedStyle* oldStyle, const ComputedStyle& newStyle) |
| { |
| if (oldStyle && newStyle.transformDataEquivalent(*oldStyle)) |
| return; |
| |
| // hasTransform() on the layoutObject is also true when there is transform-style: preserve-3d or perspective set, |
| // so check style too. |
| bool hasTransform = layoutObject()->hasTransformRelatedProperty() && newStyle.hasTransform(); |
| bool had3DTransform = has3DTransform(); |
| |
| bool hadTransform = m_transform; |
| if (hasTransform != hadTransform) { |
| if (hasTransform) |
| m_transform = adoptPtr(new TransformationMatrix); |
| else |
| m_transform.clear(); |
| |
| // PaintLayers 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 PaintLayer* enclosingLayerForContainingBlock(PaintLayer* layer) |
| { |
| if (LayoutObject* containingBlock = layer->layoutObject()->containingBlock()) |
| return containingBlock->enclosingLayer(); |
| return 0; |
| } |
| |
| PaintLayer* PaintLayer::renderingContextRoot() |
| { |
| PaintLayer* renderingContext = 0; |
| |
| if (shouldPreserve3D()) |
| renderingContext = this; |
| |
| for (PaintLayer* current = enclosingLayerForContainingBlock(this); current && current->shouldPreserve3D(); current = enclosingLayerForContainingBlock(current)) |
| renderingContext = current; |
| |
| return renderingContext; |
| } |
| |
| TransformationMatrix PaintLayer::currentTransform() const |
| { |
| if (!m_transform) |
| return TransformationMatrix(); |
| return *m_transform; |
| } |
| |
| TransformationMatrix PaintLayer::renderableTransform(GlobalPaintFlags globalPaintFlags) const |
| { |
| if (!m_transform) |
| return TransformationMatrix(); |
| |
| if (globalPaintFlags & GlobalPaintFlattenCompositingLayers) { |
| TransformationMatrix matrix = *m_transform; |
| makeMatrixRenderable(matrix, false /* flatten 3d */); |
| return matrix; |
| } |
| |
| return *m_transform; |
| } |
| |
| // Convert a bounding box from flow thread coordinates, relative to |layer|, to visual coordinates, relative to |ancestorLayer|. |
| // See http://www.chromium.org/developers/design-documents/multi-column-layout for more info on these coordinate types. |
| static void convertFromFlowThreadToVisualBoundingBoxInAncestor(const PaintLayer* layer, const PaintLayer* ancestorLayer, LayoutRect& rect) |
| { |
| PaintLayer* paginationLayer = layer->enclosingPaginationLayer(); |
| ASSERT(paginationLayer); |
| LayoutFlowThread* flowThread = toLayoutFlowThread(paginationLayer->layoutObject()); |
| |
| // First make the flow thread rectangle relative to the flow thread, not to |layer|. |
| LayoutPoint offsetWithinPaginationLayer; |
| layer->convertToLayerCoords(paginationLayer, offsetWithinPaginationLayer); |
| rect.moveBy(offsetWithinPaginationLayer); |
| |
| // Then make the rectangle visual, relative to the fragmentation context. Split our box up into |
| // the actual fragment boxes that layout in the columns/pages and unite those together to get |
| // our true bounding box. |
| rect = flowThread->fragmentsBoundingBox(rect); |
| |
| // Finally, make the visual rectangle relative to |ancestorLayer|. |
| if (ancestorLayer->enclosingPaginationLayer() != paginationLayer) { |
| rect.moveBy(paginationLayer->visualOffsetFromAncestor(ancestorLayer)); |
| return; |
| } |
| // The ancestor layer is inside the same pagination layer as |layer|, so we need to subtract |
| // the visual distance from the ancestor layer to the pagination layer. |
| rect.moveBy(-ancestorLayer->visualOffsetFromAncestor(paginationLayer)); |
| } |
| |
| void PaintLayer::updatePaginationRecursive(bool needsPaginationUpdate) |
| { |
| m_enclosingPaginationLayer = 0; |
| |
| if (layoutObject()->isLayoutFlowThread()) |
| needsPaginationUpdate = true; |
| |
| if (needsPaginationUpdate) { |
| // Each paginated layer has to paint on its own. There is no recurring into child layers. Each |
| // layer has to be checked individually and genuinely know if it is going to have to split |
| // itself up when painting only its contents (and not any other descendant layers). We track an |
| // enclosingPaginationLayer instead of using a simple bit, since we want to be able to get back |
| // to that layer easily. |
| if (LayoutFlowThread* containingFlowThread = layoutObject()->flowThreadContainingBlock()) |
| m_enclosingPaginationLayer = containingFlowThread->layer(); |
| } |
| |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->updatePaginationRecursive(needsPaginationUpdate); |
| } |
| |
| void PaintLayer::clearPaginationRecursive() |
| { |
| m_enclosingPaginationLayer = 0; |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->clearPaginationRecursive(); |
| } |
| |
| LayoutPoint PaintLayer::positionFromPaintInvalidationBacking(const LayoutObject* layoutObject, const LayoutBoxModelObject* paintInvalidationContainer, const PaintInvalidationState* paintInvalidationState) |
| { |
| FloatPoint point = layoutObject->localToContainerPoint(FloatPoint(), paintInvalidationContainer, 0, 0, paintInvalidationState); |
| |
| // FIXME: Eventually we are going to unify coordinates in GraphicsLayer space. |
| if (paintInvalidationContainer && paintInvalidationContainer->layer()->groupedMapping()) |
| mapPointToPaintBackingCoordinates(paintInvalidationContainer, point); |
| |
| return LayoutPoint(point); |
| } |
| |
| void PaintLayer::mapPointToPaintBackingCoordinates(const LayoutBoxModelObject* paintInvalidationContainer, FloatPoint& point) |
| { |
| PaintLayer* paintInvalidationLayer = paintInvalidationContainer->layer(); |
| if (!paintInvalidationLayer->groupedMapping()) { |
| point.move(paintInvalidationLayer->compositedLayerMapping()->contentOffsetInCompositingLayer()); |
| return; |
| } |
| |
| LayoutBoxModelObject* transformedAncestor = paintInvalidationLayer->enclosingTransformedAncestor()->layoutObject(); |
| if (!transformedAncestor) |
| return; |
| |
| // |paintInvalidationContainer| may have a local 2D transform on it, so take that into account when mapping into the space of the |
| // transformed ancestor. |
| point = paintInvalidationContainer->localToContainerPoint(point, transformedAncestor); |
| |
| point.moveBy(-paintInvalidationLayer->groupedMapping()->squashingOffsetFromTransformedAncestor()); |
| } |
| |
| void PaintLayer::mapRectToPaintBackingCoordinates(const LayoutBoxModelObject* paintInvalidationContainer, LayoutRect& rect) |
| { |
| PaintLayer* paintInvalidationLayer = paintInvalidationContainer->layer(); |
| if (!paintInvalidationLayer->groupedMapping()) { |
| rect.move(paintInvalidationLayer->compositedLayerMapping()->contentOffsetInCompositingLayer()); |
| return; |
| } |
| |
| LayoutBoxModelObject* transformedAncestor = paintInvalidationLayer->enclosingTransformedAncestor()->layoutObject(); |
| if (!transformedAncestor) |
| return; |
| |
| // |paintInvalidationContainer| may have a local 2D transform on it, so take that into account when mapping into the space of the |
| // transformed ancestor. |
| rect = LayoutRect(paintInvalidationContainer->localToContainerQuad(FloatRect(rect), transformedAncestor).boundingBox()); |
| |
| rect.moveBy(-paintInvalidationLayer->groupedMapping()->squashingOffsetFromTransformedAncestor()); |
| } |
| |
| void PaintLayer::mapRectToPaintInvalidationBacking(const LayoutObject* layoutObject, const LayoutBoxModelObject* paintInvalidationContainer, LayoutRect& rect, const PaintInvalidationState* paintInvalidationState) |
| { |
| if (!paintInvalidationContainer->layer()->groupedMapping()) { |
| layoutObject->mapToVisibleRectInContainerSpace(paintInvalidationContainer, rect, paintInvalidationState); |
| return; |
| } |
| |
| // This code adjusts the paint invalidation rectangle to be in the space of the transformed ancestor of the grouped (i.e. squashed) |
| // layer. This is because all layers that squash together need to issue paint invalidations w.r.t. a single container that is |
| // an ancestor of all of them, in order to properly take into account any local transforms etc. |
| // FIXME: remove this special-case code that works around the paint invalidation code structure. |
| layoutObject->mapToVisibleRectInContainerSpace(paintInvalidationContainer, rect, paintInvalidationState); |
| |
| mapRectToPaintBackingCoordinates(paintInvalidationContainer, rect); |
| } |
| |
| LayoutRect PaintLayer::computePaintInvalidationRect(const LayoutObject& layoutObject, const PaintLayer* paintInvalidationContainer, const PaintInvalidationState* paintInvalidationState) |
| { |
| if (!paintInvalidationContainer->groupedMapping()) |
| return layoutObject.computePaintInvalidationRect(*paintInvalidationContainer->layoutObject(), paintInvalidationState); |
| |
| LayoutRect rect = layoutObject.clippedOverflowRectForPaintInvalidation(paintInvalidationContainer->layoutObject(), paintInvalidationState); |
| mapRectToPaintBackingCoordinates(paintInvalidationContainer->layoutObject(), rect); |
| return rect; |
| } |
| |
| void PaintLayer::dirtyVisibleContentStatus() |
| { |
| compositor()->setNeedsUpdateDescendantDependentFlags(); |
| m_visibleContentStatusDirty = true; |
| if (parent()) |
| parent()->dirtyAncestorChainVisibleDescendantStatus(); |
| // Non-self-painting layers paint into their ancestor layer, and count as part of the "visible contents" of the parent, so we need to dirty it. |
| if (!isSelfPaintingLayer()) |
| parent()->dirtyVisibleContentStatus(); |
| } |
| |
| void PaintLayer::potentiallyDirtyVisibleContentStatus(EVisibility visibility) |
| { |
| if (m_visibleContentStatusDirty) |
| return; |
| if (hasVisibleContent() == (visibility == VISIBLE)) |
| return; |
| dirtyVisibleContentStatus(); |
| } |
| |
| void PaintLayer::dirtyAncestorChainVisibleDescendantStatus() |
| { |
| compositor()->setNeedsUpdateDescendantDependentFlags(); |
| |
| for (PaintLayer* layer = this; layer; layer = layer->parent()) { |
| if (layer->m_visibleDescendantStatusDirty) |
| break; |
| layer->m_visibleDescendantStatusDirty = true; |
| } |
| } |
| |
| // FIXME: this is quite brute-force. We could be more efficient if we were to |
| // track state and update it as appropriate as changes are made in the layout tree. |
| void PaintLayer::updateScrollingStateAfterCompositingChange() |
| { |
| TRACE_EVENT0("blink", "PaintLayer::updateScrollingStateAfterCompositingChange"); |
| m_hasVisibleNonLayerContent = false; |
| for (LayoutObject* r = layoutObject()->slowFirstChild(); r; r = r->nextSibling()) { |
| if (!r->hasLayer()) { |
| m_hasVisibleNonLayerContent = true; |
| break; |
| } |
| } |
| |
| m_hasNonCompositedChild = false; |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { |
| if (child->compositingState() == NotComposited) { |
| m_hasNonCompositedChild = true; |
| return; |
| } |
| } |
| } |
| |
| void PaintLayer::updateDescendantDependentFlags() |
| { |
| if (m_visibleDescendantStatusDirty) { |
| m_hasVisibleDescendant = false; |
| |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { |
| child->updateDescendantDependentFlags(); |
| |
| if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) { |
| m_hasVisibleDescendant = true; |
| break; |
| } |
| } |
| |
| m_visibleDescendantStatusDirty = false; |
| } |
| |
| if (m_visibleContentStatusDirty) { |
| bool previouslyHasVisibleContent = m_hasVisibleContent; |
| if (layoutObject()->style()->visibility() == VISIBLE) { |
| m_hasVisibleContent = true; |
| } else { |
| // layer may be hidden but still have some visible content, check for this |
| m_hasVisibleContent = false; |
| LayoutObject* r = layoutObject()->slowFirstChild(); |
| while (r) { |
| if (r->style()->visibility() == VISIBLE && (!r->hasLayer() || !r->enclosingLayer()->isSelfPaintingLayer())) { |
| m_hasVisibleContent = true; |
| break; |
| } |
| LayoutObject* layoutObjectFirstChild = r->slowFirstChild(); |
| if (layoutObjectFirstChild && (!r->hasLayer() || !r->enclosingLayer()->isSelfPaintingLayer())) { |
| r = layoutObjectFirstChild; |
| } else if (r->nextSibling()) { |
| r = r->nextSibling(); |
| } else { |
| do { |
| r = r->parent(); |
| if (r == layoutObject()) |
| r = 0; |
| } while (r && !r->nextSibling()); |
| if (r) |
| r = r->nextSibling(); |
| } |
| } |
| } |
| m_visibleContentStatusDirty = false; |
| |
| if (hasVisibleContent() != previouslyHasVisibleContent) { |
| setNeedsCompositingInputsUpdate(); |
| // We need to tell m_layoutObject to recheck its rect because we |
| // pretend that invisible LayoutObjects have 0x0 rects. Changing |
| // visibility therefore changes our rect and we need to visit |
| // this LayoutObject during the invalidateTreeIfNeeded walk. |
| m_layoutObject->setMayNeedPaintInvalidation(); |
| } |
| } |
| } |
| |
| void PaintLayer::dirty3DTransformedDescendantStatus() |
| { |
| PaintLayerStackingNode* 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 PaintLayer::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. |
| PaintLayerStackingNodeIterator iterator(*m_stackingNode.get(), PositiveZOrderChildren | NegativeZOrderChildren); |
| while (PaintLayerStackingNode* 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(); |
| } |
| |
| bool PaintLayer::updateLayerPosition() |
| { |
| LayoutPoint localPoint; |
| LayoutPoint inlineBoundingBoxOffset; // We don't put this into the Layer x/y for inlines, so we need to subtract it out when done. |
| |
| if (layoutObject()->isInline() && layoutObject()->isLayoutInline()) { |
| LayoutInline* inlineFlow = toLayoutInline(layoutObject()); |
| IntRect lineBox = inlineFlow->linesBoundingBox(); |
| m_size = lineBox.size(); |
| inlineBoundingBoxOffset = lineBox.location(); |
| localPoint.moveBy(inlineBoundingBoxOffset); |
| } else if (LayoutBox* box = layoutBox()) { |
| m_size = pixelSnappedIntSize(box->size(), box->location()); |
| localPoint.moveBy(box->topLeftLocation()); |
| } |
| |
| if (!layoutObject()->isOutOfFlowPositioned() && !layoutObject()->isColumnSpanAll() && layoutObject()->parent()) { |
| // We must adjust our position by walking up the layout tree looking for the |
| // nearest enclosing object with a layer. |
| LayoutObject* curr = layoutObject()->parent(); |
| while (curr && !curr->hasLayer()) { |
| if (curr->isBox() && !curr->isTableRow()) { |
| // Rows and cells share the same coordinate space (that of the section). |
| // Omit them when computing our xpos/ypos. |
| localPoint.moveBy(toLayoutBox(curr)->topLeftLocation()); |
| } |
| curr = curr->parent(); |
| } |
| if (curr->isBox() && curr->isTableRow()) { |
| // Put ourselves into the row coordinate space. |
| localPoint.moveBy(-toLayoutBox(curr)->topLeftLocation()); |
| } |
| } |
| |
| // Subtract our parent's scroll offset. |
| if (PaintLayer* positionedParent = layoutObject()->isOutOfFlowPositioned() ? enclosingPositionedAncestor() : nullptr) { |
| // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. |
| if (positionedParent->layoutObject()->hasOverflowClip()) { |
| IntSize offset = positionedParent->layoutBox()->scrolledContentOffset(); |
| localPoint -= offset; |
| } |
| |
| if (positionedParent->layoutObject()->isInFlowPositioned() && positionedParent->layoutObject()->isLayoutInline()) { |
| LayoutSize offset = toLayoutInline(positionedParent->layoutObject())->offsetForInFlowPositionedInline(*toLayoutBox(layoutObject())); |
| localPoint += offset; |
| } |
| } else if (parent() && parent()->layoutObject()->hasOverflowClip()) { |
| IntSize scrollOffset = parent()->layoutBox()->scrolledContentOffset(); |
| localPoint -= scrollOffset; |
| } |
| |
| bool positionOrOffsetChanged = false; |
| if (layoutObject()->isInFlowPositioned()) { |
| LayoutSize newOffset = layoutObject()->offsetForInFlowPosition(); |
| positionOrOffsetChanged = newOffset != m_offsetForInFlowPosition; |
| m_offsetForInFlowPosition = newOffset; |
| localPoint.move(m_offsetForInFlowPosition); |
| } else { |
| m_offsetForInFlowPosition = LayoutSize(); |
| } |
| |
| // FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the layoutObjects. |
| localPoint.moveBy(-inlineBoundingBoxOffset); |
| |
| if (m_location != localPoint) { |
| positionOrOffsetChanged = true; |
| setNeedsRepaint(); |
| } |
| m_location = localPoint; |
| |
| #if ENABLE(ASSERT) |
| m_needsPositionUpdate = false; |
| #endif |
| return positionOrOffsetChanged; |
| } |
| |
| TransformationMatrix PaintLayer::perspectiveTransform() const |
| { |
| if (!layoutObject()->hasTransformRelatedProperty()) |
| return TransformationMatrix(); |
| |
| const ComputedStyle& style = layoutObject()->styleRef(); |
| if (!style.hasPerspective()) |
| return TransformationMatrix(); |
| |
| TransformationMatrix t; |
| t.applyPerspective(style.perspective()); |
| return t; |
| } |
| |
| FloatPoint PaintLayer::perspectiveOrigin() const |
| { |
| if (!layoutObject()->hasTransformRelatedProperty()) |
| return FloatPoint(); |
| |
| const LayoutRect borderBox = toLayoutBox(layoutObject())->borderBoxRect(); |
| const ComputedStyle& style = layoutObject()->styleRef(); |
| |
| return FloatPoint(floatValueForLength(style.perspectiveOriginX(), borderBox.width().toFloat()), floatValueForLength(style.perspectiveOriginY(), borderBox.height().toFloat())); |
| } |
| |
| static inline bool isFixedPositionedContainer(PaintLayer* layer) |
| { |
| return layer->isRootLayer() || layer->hasTransformRelatedProperty(); |
| } |
| |
| PaintLayer* PaintLayer::enclosingPositionedAncestor(const PaintLayer* ancestor, bool* skippedAncestor) const |
| { |
| ASSERT(!ancestor || skippedAncestor); // If we have specified an ancestor, surely the caller needs to know whether we skipped it. |
| if (skippedAncestor) |
| *skippedAncestor = false; |
| if (layoutObject()->style()->position() == FixedPosition) { |
| PaintLayer* curr = parent(); |
| while (curr && !isFixedPositionedContainer(curr)) { |
| if (skippedAncestor && curr == ancestor) |
| *skippedAncestor = true; |
| curr = curr->parent(); |
| } |
| |
| return curr; |
| } |
| |
| PaintLayer* curr = parent(); |
| while (curr && !curr->isPositionedContainer()) { |
| if (skippedAncestor && curr == ancestor) |
| *skippedAncestor = true; |
| curr = curr->parent(); |
| } |
| |
| return curr; |
| } |
| |
| PaintLayer* PaintLayer::enclosingTransformedAncestor() const |
| { |
| PaintLayer* curr = parent(); |
| while (curr && !curr->isRootLayer() && !curr->layoutObject()->hasTransformRelatedProperty()) |
| curr = curr->parent(); |
| |
| return curr; |
| } |
| |
| LayoutPoint PaintLayer::computeOffsetFromTransformedAncestor() const |
| { |
| const AncestorDependentCompositingInputs& properties = ancestorDependentCompositingInputs(); |
| |
| TransformState transformState(TransformState::ApplyTransformDirection, FloatPoint()); |
| // FIXME: add a test that checks flipped writing mode and ApplyContainerFlip are correct. |
| layoutObject()->mapLocalToContainer(properties.transformAncestor ? properties.transformAncestor->layoutObject() : 0, transformState, ApplyContainerFlip); |
| transformState.flatten(); |
| return LayoutPoint(transformState.lastPlanarPoint()); |
| } |
| |
| PaintLayer* PaintLayer::compositingContainer() const |
| { |
| if (!stackingNode()->isTreatedAsOrStackingContext()) |
| return parent(); |
| if (PaintLayerStackingNode* ancestorStackingNode = stackingNode()->ancestorStackingContextNode()) |
| return ancestorStackingNode->layer(); |
| return nullptr; |
| } |
| |
| bool PaintLayer::isPaintInvalidationContainer() const |
| { |
| return compositingState() == PaintsIntoOwnBacking || compositingState() == PaintsIntoGroupedBacking; |
| } |
| |
| // Note: enclosingCompositingLayer does not include squashed layers. Compositing stacking children of squashed layers |
| // receive graphics layers that are parented to the compositing ancestor of the squashed layer. |
| PaintLayer* PaintLayer::enclosingLayerWithCompositedLayerMapping(IncludeSelfOrNot includeSelf) const |
| { |
| ASSERT(isAllowedToQueryCompositingState()); |
| |
| if ((includeSelf == IncludeSelf) && compositingState() != NotComposited && compositingState() != PaintsIntoGroupedBacking) |
| return const_cast<PaintLayer*>(this); |
| |
| for (PaintLayer* curr = compositingContainer(); curr; curr = curr->compositingContainer()) { |
| if (curr->compositingState() != NotComposited && curr->compositingState() != PaintsIntoGroupedBacking) |
| return curr; |
| } |
| |
| return nullptr; |
| } |
| |
| // Return the enclosingCompositedLayerForPaintInvalidation for the given Layer |
| // including crossing frame boundaries. |
| PaintLayer* PaintLayer::enclosingLayerForPaintInvalidationCrossingFrameBoundaries() const |
| { |
| const PaintLayer* layer = this; |
| PaintLayer* compositedLayer = 0; |
| while (!compositedLayer) { |
| compositedLayer = layer->enclosingLayerForPaintInvalidation(); |
| if (!compositedLayer) { |
| RELEASE_ASSERT(layer->layoutObject()->frame()); |
| LayoutObject* owner = layer->layoutObject()->frame()->ownerLayoutObject(); |
| if (!owner) |
| break; |
| layer = owner->enclosingLayer(); |
| } |
| } |
| return compositedLayer; |
| } |
| |
| PaintLayer* PaintLayer::enclosingLayerForPaintInvalidation() const |
| { |
| ASSERT(isAllowedToQueryCompositingState()); |
| |
| if (isPaintInvalidationContainer()) |
| return const_cast<PaintLayer*>(this); |
| |
| for (PaintLayer* curr = compositingContainer(); curr; curr = curr->compositingContainer()) { |
| if (curr->isPaintInvalidationContainer()) |
| return curr; |
| } |
| |
| return nullptr; |
| } |
| |
| void PaintLayer::setNeedsCompositingInputsUpdate() |
| { |
| m_needsAncestorDependentCompositingInputsUpdate = true; |
| m_needsDescendantDependentCompositingInputsUpdate = true; |
| |
| for (PaintLayer* current = this; current && !current->m_childNeedsCompositingInputsUpdate; current = current->parent()) |
| current->m_childNeedsCompositingInputsUpdate = true; |
| |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateAfterCompositingInputChange); |
| } |
| |
| void PaintLayer::updateAncestorDependentCompositingInputs(const AncestorDependentCompositingInputs& compositingInputs) |
| { |
| m_ancestorDependentCompositingInputs = compositingInputs; |
| m_needsAncestorDependentCompositingInputsUpdate = false; |
| } |
| |
| void PaintLayer::updateDescendantDependentCompositingInputs(const DescendantDependentCompositingInputs& compositingInputs) |
| { |
| m_descendantDependentCompositingInputs = compositingInputs; |
| m_needsDescendantDependentCompositingInputsUpdate = false; |
| } |
| |
| void PaintLayer::didUpdateCompositingInputs() |
| { |
| ASSERT(!needsCompositingInputsUpdate()); |
| m_childNeedsCompositingInputsUpdate = false; |
| if (m_scrollableArea) |
| m_scrollableArea->updateNeedsCompositedScrolling(); |
| } |
| |
| bool PaintLayer::hasNonIsolatedDescendantWithBlendMode() const |
| { |
| if (descendantDependentCompositingInputs().hasNonIsolatedDescendantWithBlendMode) |
| return true; |
| if (layoutObject()->isSVGRoot()) |
| return toLayoutSVGRoot(layoutObject())->hasNonIsolatedBlendingDescendants(); |
| return false; |
| } |
| |
| void PaintLayer::setCompositingReasons(CompositingReasons reasons, CompositingReasons mask) |
| { |
| if ((compositingReasons() & mask) == (reasons & mask)) |
| return; |
| m_compositingReasons = (reasons & mask) | (compositingReasons() & ~mask); |
| } |
| |
| void PaintLayer::setHasCompositingDescendant(bool hasCompositingDescendant) |
| { |
| if (m_hasCompositingDescendant == static_cast<unsigned>(hasCompositingDescendant)) |
| return; |
| |
| m_hasCompositingDescendant = hasCompositingDescendant; |
| |
| if (hasCompositedLayerMapping()) |
| compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateLocal); |
| } |
| |
| void PaintLayer::setShouldIsolateCompositedDescendants(bool shouldIsolateCompositedDescendants) |
| { |
| if (m_shouldIsolateCompositedDescendants == static_cast<unsigned>(shouldIsolateCompositedDescendants)) |
| return; |
| |
| m_shouldIsolateCompositedDescendants = shouldIsolateCompositedDescendants; |
| |
| if (hasCompositedLayerMapping()) |
| compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateLocal); |
| } |
| |
| bool PaintLayer::hasAncestorWithFilterOutsets() const |
| { |
| for (const PaintLayer* curr = this; curr; curr = curr->parent()) { |
| LayoutBoxModelObject* layoutObject = curr->layoutObject(); |
| if (layoutObject->style()->hasFilterOutsets()) |
| return true; |
| } |
| return false; |
| } |
| |
| static void expandClipRectForDescendantsAndReflection(LayoutRect& clipRect, const PaintLayer* layer, const PaintLayer* rootLayer, |
| PaintLayer::TransparencyClipBoxBehavior transparencyBehavior, const LayoutSize& subPixelAccumulation, GlobalPaintFlags globalPaintFlags) |
| { |
| // 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->layoutObject()->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 (PaintLayer* curr = layer->firstChild(); curr; curr = curr->nextSibling()) { |
| if (!layer->reflectionInfo() || layer->reflectionInfo()->reflectionLayer() != curr) |
| clipRect.unite(PaintLayer::transparencyClipBox(curr, rootLayer, transparencyBehavior, PaintLayer::DescendantsOfTransparencyClipBox, subPixelAccumulation, globalPaintFlags)); |
| } |
| } |
| |
| // If we have a reflection, then we need to account for that when we push the clip. Reflect our entire |
| // current transparencyClipBox to catch all child layers. |
| // FIXME: Accelerated compositing will eventually want to do something smart here to avoid incorporating this |
| // size into the parent layer. |
| if (layer->layoutObject()->hasReflection()) { |
| LayoutPoint delta; |
| layer->convertToLayerCoords(rootLayer, delta); |
| clipRect.move(-delta.x(), -delta.y()); |
| clipRect.unite(layer->layoutBox()->reflectedRect(clipRect)); |
| clipRect.moveBy(delta); |
| } |
| } |
| |
| LayoutRect PaintLayer::transparencyClipBox(const PaintLayer* layer, const PaintLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior, |
| TransparencyClipBoxMode transparencyMode, const LayoutSize& subPixelAccumulation, GlobalPaintFlags globalPaintFlags) |
| { |
| // 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(globalPaintFlags)) |
| || (transparencyBehavior == HitTestingTransparencyClipBox && layer->hasTransformRelatedProperty()))) { |
| // 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 PaintLayer* paginationLayer = transparencyMode == DescendantsOfTransparencyClipBox ? layer->enclosingPaginationLayer() : 0; |
| const PaintLayer* rootLayerForTransform = paginationLayer ? paginationLayer : rootLayer; |
| LayoutPoint delta; |
| layer->convertToLayerCoords(rootLayerForTransform, delta); |
| |
| delta.move(subPixelAccumulation); |
| IntPoint pixelSnappedDelta = roundedIntPoint(delta); |
| TransformationMatrix transform; |
| transform.translate(pixelSnappedDelta.x(), pixelSnappedDelta.y()); |
| if (layer->transform()) |
| 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, globalPaintFlags); |
| clipRect.expand(layer->filterOutsets()); |
| LayoutRect result = transform.mapRect(clipRect); |
| if (!paginationLayer) |
| return result; |
| |
| // We have to break up the transformed extent across our columns. |
| // Split our box up into the actual fragment boxes that layout in the columns/pages and unite those together to |
| // get our true bounding box. |
| LayoutFlowThread* enclosingFlowThread = toLayoutFlowThread(paginationLayer->layoutObject()); |
| result = enclosingFlowThread->fragmentsBoundingBox(result); |
| |
| LayoutPoint rootLayerDelta; |
| paginationLayer->convertToLayerCoords(rootLayer, rootLayerDelta); |
| result.moveBy(rootLayerDelta); |
| return result; |
| } |
| |
| LayoutRect clipRect = layer->fragmentsBoundingBox(rootLayer); |
| expandClipRectForDescendantsAndReflection(clipRect, layer, rootLayer, transparencyBehavior, subPixelAccumulation, globalPaintFlags); |
| clipRect.expand(layer->filterOutsets()); |
| clipRect.move(subPixelAccumulation); |
| return clipRect; |
| } |
| |
| LayoutRect PaintLayer::paintingExtent(const PaintLayer* rootLayer, const LayoutSize& subPixelAccumulation, GlobalPaintFlags globalPaintFlags) |
| { |
| return transparencyClipBox(this, rootLayer, PaintingTransparencyClipBox, RootOfTransparencyClipBox, subPixelAccumulation, globalPaintFlags); |
| } |
| |
| void* PaintLayer::operator new(size_t sz) |
| { |
| return partitionAlloc(WTF::Partitions::layoutPartition(), sz, WTF_HEAP_PROFILER_TYPE_NAME(PaintLayer)); |
| } |
| |
| void PaintLayer::operator delete(void* ptr) |
| { |
| partitionFree(ptr); |
| } |
| |
| void PaintLayer::addChild(PaintLayer* child, PaintLayer* beforeChild) |
| { |
| PaintLayer* 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; |
| |
| setNeedsCompositingInputsUpdate(); |
| |
| if (!child->stackingNode()->isTreatedAsOrStackingContext() && !layoutObject()->documentBeingDestroyed()) |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); |
| |
| if (child->stackingNode()->isTreatedAsOrStackingContext() || 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(); |
| } |
| |
| // Non-self-painting children paint into this layer, so the visible contents status of this layer is affected. |
| if (!child->isSelfPaintingLayer()) |
| dirtyVisibleContentStatus(); |
| |
| dirtyAncestorChainVisibleDescendantStatus(); |
| dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| |
| child->setNeedsRepaint(); |
| |
| child->updateDescendantDependentFlags(); |
| } |
| |
| PaintLayer* PaintLayer::removeChild(PaintLayer* 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()->isTreatedAsOrStackingContext() && !layoutObject()->documentBeingDestroyed()) |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree); |
| |
| if (oldChild->stackingNode()->isTreatedAsOrStackingContext() || 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(); |
| } |
| |
| if (layoutObject()->style()->visibility() != VISIBLE) |
| dirtyVisibleContentStatus(); |
| |
| oldChild->setPreviousSibling(0); |
| oldChild->setNextSibling(0); |
| oldChild->m_parent = 0; |
| |
| dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| |
| oldChild->updateDescendantDependentFlags(); |
| |
| if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant) |
| dirtyAncestorChainVisibleDescendantStatus(); |
| |
| if (oldChild->enclosingPaginationLayer()) |
| oldChild->clearPaginationRecursive(); |
| |
| setNeedsRepaint(); |
| |
| return oldChild; |
| } |
| |
| void PaintLayer::removeOnlyThisLayer() |
| { |
| if (!m_parent) |
| return; |
| |
| if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled()) { |
| DisableCompositingQueryAsserts disabler; // We need the current compositing status. |
| if (isPaintInvalidationContainer()) { |
| // Our children will be reparented and contained by a new paint invalidation container, |
| // so need paint invalidation. CompositingUpdate can't see this layer (which has been |
| // removed) so won't do this for us. |
| DisablePaintInvalidationStateAsserts disabler; |
| layoutObject()->invalidatePaintIncludingNonCompositingDescendants(); |
| layoutObject()->setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants(); |
| } |
| } |
| |
| m_clipper.clearClipRectsIncludingDescendants(); |
| |
| PaintLayer* nextSib = nextSibling(); |
| |
| // Remove the child reflection layer before moving other child layers. |
| // The reflection layer should not be moved to the parent. |
| if (m_reflectionInfo) |
| removeChild(m_reflectionInfo->reflectionLayer()); |
| |
| // Now walk our kids and reattach them to our parent. |
| PaintLayer* current = m_first; |
| while (current) { |
| PaintLayer* 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_layoutObject->destroyLayer(); |
| } |
| |
| void PaintLayer::insertOnlyThisLayer() |
| { |
| if (!m_parent && layoutObject()->parent()) { |
| // We need to connect ourselves when our layoutObject() has a parent. |
| // Find our enclosingLayer and add ourselves. |
| PaintLayer* parentLayer = layoutObject()->parent()->enclosingLayer(); |
| ASSERT(parentLayer); |
| PaintLayer* beforeChild = !parentLayer->reflectionInfo() || parentLayer->reflectionInfo()->reflectionLayer() != this ? layoutObject()->parent()->findNextLayer(parentLayer, layoutObject()) : 0; |
| parentLayer->addChild(this, beforeChild); |
| } |
| |
| // Remove all descendant layers from the hierarchy and add them to the new position. |
| for (LayoutObject* curr = layoutObject()->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 PaintLayer* accumulateOffsetTowardsAncestor(const PaintLayer* layer, const PaintLayer* ancestorLayer, LayoutPoint& location) |
| { |
| ASSERT(ancestorLayer != layer); |
| |
| const LayoutBoxModelObject* layoutObject = layer->layoutObject(); |
| EPosition position = layoutObject->style()->position(); |
| |
| if (position == FixedPosition && (!ancestorLayer || ancestorLayer == layoutObject->view()->layer())) { |
| // If the fixed layer's container is the root, just add in the offset of the view. We can obtain this by calling |
| // localToAbsolute() on the LayoutView. |
| FloatPoint absPos = layoutObject->localToAbsolute(FloatPoint(), IsFixed); |
| location += LayoutSize(absPos.x(), absPos.y()); |
| return ancestorLayer; |
| } |
| |
| PaintLayer* parentLayer; |
| if (position == AbsolutePosition || position == FixedPosition) { |
| bool foundAncestorFirst; |
| parentLayer = layer->enclosingPositionedAncestor(ancestorLayer, &foundAncestorFirst); |
| |
| if (foundAncestorFirst) { |
| // Found ancestorLayer before the container of the out-of-flow object, so compute offset |
| // of both relative to the container and subtract. |
| |
| LayoutPoint thisCoords; |
| layer->convertToLayerCoords(parentLayer, thisCoords); |
| |
| LayoutPoint ancestorCoords; |
| ancestorLayer->convertToLayerCoords(parentLayer, ancestorCoords); |
| |
| location += (thisCoords - ancestorCoords); |
| return ancestorLayer; |
| } |
| } else if (layoutObject->isColumnSpanAll()) { |
| LayoutBlock* multicolContainer = layoutObject->containingBlock(); |
| ASSERT(toLayoutBlockFlow(multicolContainer)->multiColumnFlowThread()); |
| parentLayer = multicolContainer->layer(); |
| ASSERT(parentLayer); |
| } else { |
| parentLayer = layer->parent(); |
| } |
| |
| if (!parentLayer) |
| return nullptr; |
| |
| location += layer->location(); |
| return parentLayer; |
| } |
| |
| void PaintLayer::convertToLayerCoords(const PaintLayer* ancestorLayer, LayoutPoint& location) const |
| { |
| if (ancestorLayer == this) |
| return; |
| |
| const PaintLayer* currLayer = this; |
| while (currLayer && currLayer != ancestorLayer) |
| currLayer = accumulateOffsetTowardsAncestor(currLayer, ancestorLayer, location); |
| } |
| |
| void PaintLayer::convertToLayerCoords(const PaintLayer* ancestorLayer, LayoutRect& rect) const |
| { |
| LayoutPoint delta; |
| convertToLayerCoords(ancestorLayer, delta); |
| rect.moveBy(delta); |
| } |
| |
| LayoutPoint PaintLayer::visualOffsetFromAncestor(const PaintLayer* ancestorLayer) const |
| { |
| LayoutPoint offset; |
| if (ancestorLayer == this) |
| return offset; |
| PaintLayer* paginationLayer = enclosingPaginationLayer(); |
| if (paginationLayer == this) |
| paginationLayer = parent()->enclosingPaginationLayer(); |
| if (!paginationLayer) { |
| convertToLayerCoords(ancestorLayer, offset); |
| return offset; |
| } |
| |
| LayoutFlowThread* flowThread = toLayoutFlowThread(paginationLayer->layoutObject()); |
| convertToLayerCoords(paginationLayer, offset); |
| offset = flowThread->flowThreadPointToVisualPoint(offset); |
| if (ancestorLayer == paginationLayer) |
| return offset; |
| |
| if (ancestorLayer->enclosingPaginationLayer() != paginationLayer) { |
| offset.moveBy(paginationLayer->visualOffsetFromAncestor(ancestorLayer)); |
| } else { |
| // The ancestor layer is also inside the pagination layer, so we need to subtract the visual |
| // distance from the ancestor layer to the pagination layer. |
| offset.moveBy(-ancestorLayer->visualOffsetFromAncestor(paginationLayer)); |
| } |
| return offset; |
| } |
| |
| void PaintLayer::didUpdateNeedsCompositedScrolling() |
| { |
| updateSelfPaintingLayer(); |
| } |
| |
| void PaintLayer::updateReflectionInfo(const ComputedStyle* oldStyle) |
| { |
| ASSERT(!oldStyle || !layoutObject()->style()->reflectionDataEquivalent(oldStyle)); |
| if (layoutObject()->hasReflection()) { |
| if (!m_reflectionInfo) |
| m_reflectionInfo = adoptPtr(new PaintLayerReflectionInfo(*layoutBox())); |
| m_reflectionInfo->updateAfterStyleChange(oldStyle); |
| } else if (m_reflectionInfo) { |
| m_reflectionInfo->destroy(); |
| m_reflectionInfo = nullptr; |
| } |
| } |
| |
| void PaintLayer::updateStackingNode() |
| { |
| ASSERT(!m_stackingNode); |
| if (requiresStackingNode()) |
| m_stackingNode = adoptPtr(new PaintLayerStackingNode(this)); |
| else |
| m_stackingNode = nullptr; |
| } |
| |
| void PaintLayer::updateScrollableArea() |
| { |
| ASSERT(!m_scrollableArea); |
| if (requiresScrollableArea()) |
| m_scrollableArea = PaintLayerScrollableArea::create(*this); |
| } |
| |
| bool PaintLayer::hasOverflowControls() const |
| { |
| return m_scrollableArea && (m_scrollableArea->hasScrollbar() || m_scrollableArea->scrollCorner() || layoutObject()->style()->resize() != RESIZE_NONE); |
| } |
| |
| void PaintLayer::appendSingleFragmentIgnoringPagination(PaintLayerFragments& fragments, const PaintLayer* rootLayer, const LayoutRect& dirtyRect, ClipRectsCacheSlot clipRectsCacheSlot, OverlayScrollbarSizeRelevancy inOverlayScrollbarSizeRelevancy, ShouldRespectOverflowClip respectOverflowClip, const LayoutPoint* offsetFromRoot, const LayoutSize& subPixelAccumulation) |
| { |
| PaintLayerFragment fragment; |
| ClipRectsContext clipRectsContext(rootLayer, clipRectsCacheSlot, inOverlayScrollbarSizeRelevancy, subPixelAccumulation); |
| if (respectOverflowClip == IgnoreOverflowClip) |
| clipRectsContext.setIgnoreOverflowClip(); |
| clipper().calculateRects(clipRectsContext, dirtyRect, fragment.layerBounds, fragment.backgroundRect, fragment.foregroundRect, offsetFromRoot); |
| fragments.append(fragment); |
| } |
| |
| void PaintLayer::collectFragments(PaintLayerFragments& fragments, const PaintLayer* rootLayer, const LayoutRect& dirtyRect, |
| ClipRectsCacheSlot clipRectsCacheSlot, OverlayScrollbarSizeRelevancy inOverlayScrollbarSizeRelevancy, ShouldRespectOverflowClip respectOverflowClip, const LayoutPoint* offsetFromRoot, |
| const LayoutSize& subPixelAccumulation, const LayoutRect* layerBoundingBox) |
| { |
| if (!enclosingPaginationLayer()) { |
| // For unpaginated layers, there is only one fragment. |
| appendSingleFragmentIgnoringPagination(fragments, rootLayer, dirtyRect, clipRectsCacheSlot, inOverlayScrollbarSizeRelevancy, respectOverflowClip, offsetFromRoot, subPixelAccumulation); |
| return; |
| } |
| |
| // Compute our offset within the enclosing pagination layer. |
| LayoutPoint offsetWithinPaginatedLayer; |
| convertToLayerCoords(enclosingPaginationLayer(), offsetWithinPaginatedLayer); |
| |
| // Calculate clip rects relative to the enclosingPaginationLayer. The purpose of this call is to determine our bounds clipped to intermediate |
| // layers between us and the pagination context. It's important to minimize the number of fragments we need to create and this helps with that. |
| ClipRectsContext paginationClipRectsContext(enclosingPaginationLayer(), clipRectsCacheSlot, inOverlayScrollbarSizeRelevancy); |
| if (respectOverflowClip == IgnoreOverflowClip) |
| paginationClipRectsContext.setIgnoreOverflowClip(); |
| LayoutRect layerBoundsInFlowThread; |
| ClipRect backgroundRectInFlowThread; |
| ClipRect foregroundRectInFlowThread; |
| clipper().calculateRects(paginationClipRectsContext, LayoutRect(LayoutRect::infiniteIntRect()), layerBoundsInFlowThread, |
| backgroundRectInFlowThread, foregroundRectInFlowThread, &offsetWithinPaginatedLayer); |
| |
| // Take our bounding box within the flow thread and clip it. |
| LayoutRect layerBoundingBoxInFlowThread = layerBoundingBox ? *layerBoundingBox : physicalBoundingBox(offsetWithinPaginatedLayer); |
| layerBoundingBoxInFlowThread.intersect(backgroundRectInFlowThread.rect()); |
| |
| // Make the dirty rect relative to the fragmentation context (multicol container, etc.). |
| LayoutFlowThread* enclosingFlowThread = toLayoutFlowThread(enclosingPaginationLayer()->layoutObject()); |
| LayoutPoint offsetOfPaginationLayerFromRoot; // Visual offset from the root layer to the nearest fragmentation context. |
| bool rootLayerIsInsidePaginationLayer = rootLayer->enclosingPaginationLayer() == enclosingPaginationLayer(); |
| if (rootLayerIsInsidePaginationLayer) { |
| // The root layer is in the same fragmentation context as this layer, so we need to look |
| // inside it and subtract the offset between the fragmentation context and the root layer. |
| offsetOfPaginationLayerFromRoot = -rootLayer->visualOffsetFromAncestor(enclosingPaginationLayer()); |
| } else { |
| offsetOfPaginationLayerFromRoot = enclosingPaginationLayer()->visualOffsetFromAncestor(rootLayer); |
| } |
| LayoutRect dirtyRectInFlowThread(dirtyRect); |
| dirtyRectInFlowThread.moveBy(-offsetOfPaginationLayerFromRoot); |
| |
| // Tell the flow thread to collect the fragments. We pass enough information to create a minimal number of fragments based off the pages/columns |
| // that intersect the actual dirtyRect as well as the pages/columns that intersect our layer's bounding box. |
| enclosingFlowThread->collectLayerFragments(fragments, layerBoundingBoxInFlowThread, dirtyRectInFlowThread); |
| |
| if (fragments.isEmpty()) |
| return; |
| |
| // Get the parent clip rects of the pagination layer, since we need to intersect with that when painting column contents. |
| ClipRect ancestorClipRect = dirtyRect; |
| if (const PaintLayer* paginationParentLayer = enclosingPaginationLayer()->parent()) { |
| const PaintLayer* ancestorLayer = rootLayerIsInsidePaginationLayer ? paginationParentLayer : rootLayer; |
| ClipRectsContext clipRectsContext(ancestorLayer, clipRectsCacheSlot, inOverlayScrollbarSizeRelevancy); |
| if (respectOverflowClip == IgnoreOverflowClip) |
| clipRectsContext.setIgnoreOverflowClip(); |
| ancestorClipRect = enclosingPaginationLayer()->clipper().backgroundClipRect(clipRectsContext); |
| if (rootLayerIsInsidePaginationLayer) |
| ancestorClipRect.moveBy(-rootLayer->visualOffsetFromAncestor(ancestorLayer)); |
| ancestorClipRect.intersect(dirtyRect); |
| } |
| |
| const LayoutSize subPixelAccumulationIfNeeded = offsetFromRoot ? subPixelAccumulation : LayoutSize(); |
| for (size_t i = 0; i < fragments.size(); ++i) { |
| PaintLayerFragment& fragment = fragments.at(i); |
| |
| // Set our four rects with all clipping applied that was internal to the flow thread. |
| fragment.setRects(layerBoundsInFlowThread, backgroundRectInFlowThread, foregroundRectInFlowThread); |
| |
| // Shift to the root-relative physical position used when painting the flow thread in this fragment. |
| fragment.moveBy(fragment.paginationOffset + offsetOfPaginationLayerFromRoot + subPixelAccumulationIfNeeded); |
| |
| // Intersect the fragment with our ancestor's background clip so that e.g., columns in an overflow:hidden block are |
| // properly clipped by the overflow. |
| fragment.intersect(ancestorClipRect.rect()); |
| |
| // Now intersect with our pagination clip. This will typically mean we're just intersecting the dirty rect with the column |
| // clip, so the column clip ends up being all we apply. |
| fragment.intersect(fragment.paginationClip); |
| } |
| } |
| |
| static inline LayoutRect frameVisibleRect(LayoutObject* layoutObject) |
| { |
| FrameView* frameView = layoutObject->document().view(); |
| if (!frameView) |
| return LayoutRect(); |
| |
| return LayoutRect(frameView->visibleContentRect()); |
| } |
| |
| bool PaintLayer::hitTest(HitTestResult& result) |
| { |
| ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant()); |
| |
| // LayoutView should make sure to update layout before entering hit testing |
| ASSERT(!layoutObject()->frame()->view()->layoutPending()); |
| ASSERT(!layoutObject()->document().layoutView()->needsLayout()); |
| |
| const HitTestRequest& request = result.hitTestRequest(); |
| const HitTestLocation& hitTestLocation = result.hitTestLocation(); |
| |
| // Start with frameVisibleRect to ensure we include the scrollbars. |
| LayoutRect hitTestArea = frameVisibleRect(layoutObject()); |
| if (request.ignoreClipping()) |
| hitTestArea.unite(LayoutRect(layoutObject()->view()->documentRect())); |
| |
| PaintLayer* insideLayer = hitTestLayer(this, 0, result, hitTestArea, hitTestLocation, false); |
| if (!insideLayer && isRootLayer()) { |
| IntRect hitRect = hitTestLocation.boundingBox(); |
| bool fallback = false; |
| // If we didn't hit any layers but are still inside the document |
| // bounds, then we should fallback to hitting the document. |
| // For rect-based hit test, we do the fallback only when the hit-rect |
| // is totally within the document bounds. |
| if (hitTestArea.contains(LayoutRect(hitRect))) { |
| fallback = true; |
| |
| // Mouse dragging outside the main document should also be |
| // delivered to the document. |
| // TODO(miletus): Capture behavior inconsistent with iframes |
| // crbug.com/522109. |
| // TODO(majidvp): This should apply more consistently across different event types and we |
| // should not use RequestType for it. Perhaps best for it to be done at a higher level. See |
| // http://crbug.com/505825 |
| } else if ((request.active() || request.release()) && !request.isChildFrameHitTest()) { |
| fallback = true; |
| } |
| if (fallback) { |
| layoutObject()->updateHitTestResult(result, toLayoutView(layoutObject())->flipForWritingMode(hitTestLocation.point())); |
| insideLayer = this; |
| |
| // Don't cache this result since it really wasn't a true hit. |
| result.setCacheable(false); |
| } |
| } |
| |
| // 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* PaintLayer::enclosingNode() const |
| { |
| for (LayoutObject* r = layoutObject(); r; r = r->parent()) { |
| if (Node* e = r->node()) |
| return e; |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| bool PaintLayer::isInTopLayer() const |
| { |
| Node* node = layoutObject()->node(); |
| return node && node->isElementNode() && toElement(node)->isInTopLayer(); |
| } |
| |
| // 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> PaintLayer::createLocalTransformState(PaintLayer* rootLayer, PaintLayer* 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(FloatRect(hitTestRect))); |
| convertToLayerCoords(rootLayer, offset); |
| } |
| offset.moveBy(translationOffset); |
| |
| LayoutObject* containerLayoutObject = containerLayer ? containerLayer->layoutObject() : 0; |
| if (layoutObject()->shouldUseTransformFromContainer(containerLayoutObject)) { |
| TransformationMatrix containerTransform; |
| layoutObject()->getTransformFromContainer(containerLayoutObject, toLayoutSize(offset), containerTransform); |
| transformState->applyTransform(containerTransform, HitTestingTransformState::AccumulateTransform); |
| } else { |
| transformState->translate(offset.x(), offset.y(), HitTestingTransformState::AccumulateTransform); |
| } |
| |
| return transformState; |
| } |
| |
| |
| static bool isHitCandidate(const PaintLayer* 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. |
| PaintLayer* PaintLayer::hitTestLayer(PaintLayer* rootLayer, PaintLayer* containerLayer, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, bool appliedTransform, |
| const HitTestingTransformState* transformState, double* zOffset) |
| { |
| ASSERT(layoutObject()->document().lifecycle().state() >= DocumentLifecycle::CompositingClean); |
| |
| if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant()) |
| return 0; |
| |
| ClipRectsCacheSlot clipRectsCacheSlot = result.hitTestRequest().ignoreClipping() ? RootRelativeClipRectsIgnoringViewportClip : RootRelativeClipRects; |
| |
| // Apply a transform if we have one. |
| if (transform() && !appliedTransform) { |
| if (enclosingPaginationLayer()) |
| return hitTestTransformedLayerInFragments(rootLayer, containerLayer, result, hitTestRect, hitTestLocation, transformState, zOffset, clipRectsCacheSlot); |
| |
| // Make sure the parent's clip rects have been calculated. |
| if (parent()) { |
| ClipRect clipRect = clipper().backgroundClipRect(ClipRectsContext(rootLayer, clipRectsCacheSlot, IncludeOverlayScrollbarSize)); |
| // Go ahead and test the enclosing clip now. |
| if (!clipRect.intersects(hitTestLocation)) |
| return 0; |
| } |
| |
| return hitTestLayerByApplyingTransform(rootLayer, containerLayer, result, hitTestRect, hitTestLocation, transformState, zOffset); |
| } |
| |
| // Ensure our lists and 3d status are up-to-date. |
| m_stackingNode->updateLayerListsIfNeeded(); |
| update3DTransformedDescendantStatus(); |
| |
| // The natural thing would be to keep HitTestingTransformState on the stack, but it's big, so we heap-allocate. |
| 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 && layoutObject()->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. |
| PaintLayer* candidateLayer = 0; |
| |
| // Begin by walking our list of positive layers from highest z-index down to the lowest z-index. |
| PaintLayer* hitLayer = hitTestChildren(PositiveZOrderChildren, rootLayer, 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, result, hitTestRect, hitTestLocation, |
| localTransformState.get(), zOffsetForDescendantsPtr, zOffset, unflattenedTransformState.get(), depthSortDescendants); |
| if (hitLayer) { |
| if (!depthSortDescendants) |
| return hitLayer; |
| candidateLayer = hitLayer; |
| } |
| |
| // Collect the fragments. This will compute the clip rectangles for each layer fragment. |
| PaintLayerFragments layerFragments; |
| if (appliedTransform) |
| appendSingleFragmentIgnoringPagination(layerFragments, rootLayer, hitTestRect, clipRectsCacheSlot, IncludeOverlayScrollbarSize); |
| else |
| collectFragments(layerFragments, rootLayer, hitTestRect, clipRectsCacheSlot, IncludeOverlayScrollbarSize); |
| |
| if (m_scrollableArea && m_scrollableArea->hitTestResizerInFragments(layerFragments, hitTestLocation)) { |
| layoutObject()->updateHitTestResult(result, hitTestLocation.point()); |
| return this; |
| } |
| |
| // Next we want to see if the mouse pos is inside the child LayoutObjects of the layer. Check |
| // every fragment in reverse order. |
| if (isSelfPaintingLayer()) { |
| // Hit test with a temporary HitTestResult, because we only want to commit to 'result' if we know we're frontmost. |
| HitTestResult tempResult(result.hitTestRequest(), result.hitTestLocation()); |
| bool insideFragmentForegroundRect = false; |
| if (hitTestContentsForFragments(layerFragments, tempResult, hitTestLocation, HitTestDescendants, insideFragmentForegroundRect) |
| && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { |
| if (result.hitTestRequest().listBased()) |
| 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 (insideFragmentForegroundRect && result.hitTestRequest().listBased()) { |
| result.append(tempResult); |
| } |
| } |
| |
| // Now check our negative z-index children. |
| hitLayer = hitTestChildren(NegativeZOrderChildren, rootLayer, 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()) { |
| HitTestResult tempResult(result.hitTestRequest(), result.hitTestLocation()); |
| bool insideFragmentBackgroundRect = false; |
| if (hitTestContentsForFragments(layerFragments, tempResult, hitTestLocation, HitTestSelf, insideFragmentBackgroundRect) |
| && isHitCandidate(this, false, zOffsetForContentsPtr, unflattenedTransformState.get())) { |
| if (result.isRectBasedTest()) |
| result.append(tempResult); |
| else |
| result = tempResult; |
| return this; |
| } |
| if (insideFragmentBackgroundRect && result.hitTestRequest().listBased()) |
| result.append(tempResult); |
| } |
| |
| return 0; |
| } |
| |
| bool PaintLayer::hitTestContentsForFragments(const PaintLayerFragments& layerFragments, HitTestResult& result, |
| const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter, bool& insideClipRect) const |
| { |
| if (layerFragments.isEmpty()) |
| return false; |
| |
| for (int i = layerFragments.size() - 1; i >= 0; --i) { |
| const PaintLayerFragment& fragment = layerFragments.at(i); |
| if ((hitTestFilter == HitTestSelf && !fragment.backgroundRect.intersects(hitTestLocation)) |
| || (hitTestFilter == HitTestDescendants && !fragment.foregroundRect.intersects(hitTestLocation))) |
| continue; |
| insideClipRect = true; |
| if (hitTestContents(result, fragment.layerBounds, hitTestLocation, hitTestFilter)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| PaintLayer* PaintLayer::hitTestTransformedLayerInFragments(PaintLayer* rootLayer, PaintLayer* containerLayer, HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, const HitTestingTransformState* transformState, double* zOffset, ClipRectsCacheSlot clipRectsCacheSlot) |
| { |
| PaintLayerFragments enclosingPaginationFragments; |
| LayoutPoint offsetOfPaginationLayerFromRoot; |
| // FIXME: We're missing a sub-pixel offset here crbug.com/348728 |
| LayoutRect transformedExtent = transparencyClipBox(this, enclosingPaginationLayer(), HitTestingTransparencyClipBox, PaintLayer::RootOfTransparencyClipBox, LayoutSize()); |
| enclosingPaginationLayer()->collectFragments(enclosingPaginationFragments, rootLayer, hitTestRect, |
| clipRectsCacheSlot, IncludeOverlayScrollbarSize, RespectOverflowClip, &offsetOfPaginationLayerFromRoot, LayoutSize(), &transformedExtent); |
| |
| for (int i = enclosingPaginationFragments.size() - 1; i >= 0; --i) { |
| const PaintLayerFragment& fragment = enclosingPaginationFragments.at(i); |
| |
| // Apply the page/column clip for this fragment, as well as any clips established by layers in between us and |
| // the enclosing pagination layer. |
| LayoutRect clipRect = fragment.backgroundRect.rect(); |
| |
| // Now compute the clips within a given fragment |
| if (parent() != enclosingPaginationLayer()) { |
| enclosingPaginationLayer()->convertToLayerCoords(rootLayer, offsetOfPaginationLayerFromRoot); |
| LayoutRect parentClipRect = clipper().backgroundClipRect(ClipRectsContext(enclosingPaginationLayer(), clipRectsCacheSlot, IncludeOverlayScrollbarSize)).rect(); |
| parentClipRect.moveBy(fragment.paginationOffset + offsetOfPaginationLayerFromRoot); |
| clipRect.intersect(parentClipRect); |
| } |
| |
| if (!hitTestLocation.intersects(clipRect)) |
| continue; |
| |
| PaintLayer* hitLayer = hitTestLayerByApplyingTransform(rootLayer, containerLayer, result, hitTestRect, hitTestLocation, |
| transformState, zOffset, fragment.paginationOffset); |
| if (hitLayer) |
| return hitLayer; |
| } |
| |
| return 0; |
| } |
| |
| PaintLayer* PaintLayer::hitTestLayerByApplyingTransform(PaintLayer* rootLayer, PaintLayer* containerLayer, 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, result, localHitTestRect, newHitTestLocation, true, newTransformState.get(), zOffset); |
| } |
| |
| bool PaintLayer::hitTestContents(HitTestResult& result, const LayoutRect& layerBounds, const HitTestLocation& hitTestLocation, HitTestFilter hitTestFilter) const |
| { |
| ASSERT(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant()); |
| |
| if (!layoutObject()->hitTest(result, hitTestLocation, toLayoutPoint(layerBounds.location() - layoutBoxLocation()), 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.hitTestRequest().listBased() && result.listBasedTestResult().size())); |
| return false; |
| } |
| |
| if (!result.innerNode()) { |
| // We hit something anonymous, and we didn't find a DOM node ancestor in this layer. |
| |
| if (layoutObject()->isLayoutFlowThread()) { |
| // For a flow thread it's safe to just say that we didn't hit anything. That means that |
| // we'll continue as normally, and eventually hit a column set sibling instead. Column |
| // sets are also anonymous, but, unlike flow threads, they don't establish layers, so |
| // we'll fall back and hit the multicol container parent (which should have a DOM node). |
| return false; |
| } |
| |
| Node* e = enclosingNode(); |
| // FIXME: should be a call to result.setNodeAndPosition. What we would really want to do here is to |
| // return and look for the nearest non-anonymous ancestor, and ignore aunts and uncles on |
| // our way. It's bad to look for it manually like we do here, and give up on setting a local |
| // point in the result, because that has bad implications for text selection and |
| // caretRangeFromPoint(). See crbug.com/461791 |
| if (!result.innerNode()) |
| result.setInnerNode(e); |
| |
| } |
| return true; |
| } |
| |
| PaintLayer* PaintLayer::hitTestChildren(ChildrenIteration childrentoVisit, PaintLayer* rootLayer, |
| HitTestResult& result, |
| const LayoutRect& hitTestRect, const HitTestLocation& hitTestLocation, |
| const HitTestingTransformState* transformState, |
| double* zOffsetForDescendants, double* zOffset, |
| const HitTestingTransformState* unflattenedTransformState, |
| bool depthSortDescendants) |
| { |
| if (!hasSelfPaintingLayerDescendant()) |
| return 0; |
| |
| PaintLayer* resultLayer = 0; |
| PaintLayerStackingNodeReverseIterator iterator(*m_stackingNode, childrentoVisit); |
| while (PaintLayerStackingNode* child = iterator.next()) { |
| PaintLayer* childLayer = child->layer(); |
| PaintLayer* hitLayer = 0; |
| HitTestResult tempResult(result.hitTestRequest(), result.hitTestLocation()); |
| hitLayer = childLayer->hitTestLayer(rootLayer, this, tempResult, hitTestRect, hitTestLocation, false, transformState, zOffsetForDescendants); |
| |
| // If it is a list-based test, we can safely append the temporary result since it might had hit |
| // nodes but not necesserily had hitLayer set. |
| ASSERT(!result.isRectBasedTest() || result.hitTestRequest().listBased()); |
| if (result.hitTestRequest().listBased()) |
| result.append(tempResult); |
| |
| if (isHitCandidate(hitLayer, depthSortDescendants, zOffset, unflattenedTransformState)) { |
| resultLayer = hitLayer; |
| if (!result.hitTestRequest().listBased()) |
| result = tempResult; |
| if (!depthSortDescendants) |
| break; |
| } |
| } |
| |
| return resultLayer; |
| } |
| |
| void PaintLayer::blockSelectionGapsBoundsChanged() |
| { |
| if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| return; |
| |
| setNeedsCompositingInputsUpdate(); |
| } |
| |
| void PaintLayer::addBlockSelectionGapsBounds(const LayoutRect& bounds) |
| { |
| if (RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()) |
| return; |
| m_blockSelectionGapsBounds.unite(enclosingIntRect(bounds)); |
| blockSelectionGapsBoundsChanged(); |
| } |
| |
| void PaintLayer::clearBlockSelectionGapsBounds() |
| { |
| if (RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()) |
| return; |
| m_blockSelectionGapsBounds = IntRect(); |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->clearBlockSelectionGapsBounds(); |
| blockSelectionGapsBoundsChanged(); |
| } |
| |
| void PaintLayer::invalidatePaintForBlockSelectionGaps() |
| { |
| if (RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()) |
| return; |
| |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) { |
| // FIXME: We should not allow paint invalidation out of paint invalidation state. crbug.com/457415 |
| DisablePaintInvalidationStateAsserts disabler; |
| child->invalidatePaintForBlockSelectionGaps(); |
| } |
| |
| if (m_blockSelectionGapsBounds.isEmpty()) |
| return; |
| |
| LayoutRect rect(m_blockSelectionGapsBounds); |
| if (layoutObject()->hasOverflowClip()) { |
| LayoutBox* box = layoutBox(); |
| rect.move(-box->scrolledContentOffset()); |
| if (!scrollableArea()->usesCompositedScrolling()) |
| rect.intersect(box->overflowClipRect(LayoutPoint())); |
| } |
| if (layoutObject()->hasClip()) |
| rect.intersect(toLayoutBox(layoutObject())->clipRect(LayoutPoint())); |
| if (!rect.isEmpty()) { |
| // FIXME: We should not allow paint invalidation out of paint invalidation state. crbug.com/457415 |
| DisablePaintInvalidationStateAsserts disabler; |
| layoutObject()->invalidatePaintRectangle(rect); |
| } |
| } |
| |
| IntRect PaintLayer::blockSelectionGapsBounds() const |
| { |
| if (RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()) |
| return IntRect(); |
| |
| if (!layoutObject()->isLayoutBlockFlow()) |
| return IntRect(); |
| |
| LayoutBlockFlow* layoutBlockFlow = toLayoutBlockFlow(layoutObject()); |
| LayoutRect gapRects = layoutBlockFlow->selectionGapRectsForPaintInvalidation(layoutBlockFlow); |
| |
| return pixelSnappedIntRect(gapRects); |
| } |
| |
| bool PaintLayer::hasBlockSelectionGapBounds() const |
| { |
| if (RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled()) |
| return false; |
| |
| // FIXME: it would be more accurate to return !blockSelectionGapsBounds().isEmpty(), but this is impossible |
| // at the moment because it causes invalid queries to layout-dependent code (crbug.com/372802). |
| // ASSERT(layoutObject()->document().lifecycle().state() >= DocumentLifecycle::LayoutClean); |
| |
| if (!layoutObject()->isLayoutBlock()) |
| return false; |
| |
| return toLayoutBlock(layoutObject())->shouldPaintSelectionGaps(); |
| } |
| |
| bool PaintLayer::intersectsDamageRect(const LayoutRect& layerBounds, const LayoutRect& damageRect, 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 LayoutView |
| // paints the root's background. |
| if (isRootLayer() || layoutObject()->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. |
| LayoutView* view = layoutObject()->view(); |
| ASSERT(view); |
| if (view && !layoutObject()->isLayoutInline()) { |
| 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(offsetFromRoot).intersects(damageRect); |
| } |
| |
| LayoutRect PaintLayer::logicalBoundingBox() const |
| { |
| return layoutObject()->visualOverflowRect(); |
| } |
| |
| static inline LayoutRect flippedLogicalBoundingBox(LayoutRect boundingBox, LayoutObject* layoutObjects) |
| { |
| LayoutRect result = boundingBox; |
| if (layoutObjects->isBox()) |
| toLayoutBox(layoutObjects)->flipForWritingMode(result); |
| else |
| layoutObjects->containingBlock()->flipForWritingMode(result); |
| return result; |
| } |
| |
| LayoutRect PaintLayer::physicalBoundingBox(const PaintLayer* ancestorLayer) const |
| { |
| LayoutPoint offsetFromRoot; |
| convertToLayerCoords(ancestorLayer, offsetFromRoot); |
| return physicalBoundingBox(offsetFromRoot); |
| } |
| |
| LayoutRect PaintLayer::physicalBoundingBox(const LayoutPoint& offsetFromRoot) const |
| { |
| LayoutRect result = flippedLogicalBoundingBox(logicalBoundingBox(), layoutObject()); |
| result.moveBy(offsetFromRoot); |
| return result; |
| } |
| |
| LayoutRect PaintLayer::fragmentsBoundingBox(const PaintLayer* ancestorLayer) const |
| { |
| if (!enclosingPaginationLayer()) |
| return physicalBoundingBox(ancestorLayer); |
| |
| LayoutRect result = flippedLogicalBoundingBox(logicalBoundingBox(), layoutObject()); |
| convertFromFlowThreadToVisualBoundingBoxInAncestor(this, ancestorLayer, result); |
| return result; |
| } |
| |
| LayoutRect PaintLayer::boundingBoxForCompositingOverlapTest() const |
| { |
| // Apply NeverIncludeTransformForAncestorLayer, because the geometry map in CompositingInputsUpdater will take care of applying the |
| // transform of |this| (== the ancestorLayer argument to boundingBoxForCompositing). |
| return overlapBoundsIncludeChildren() ? boundingBoxForCompositing(this, NeverIncludeTransformForAncestorLayer) : fragmentsBoundingBox(this); |
| } |
| |
| static void expandRectForReflectionAndStackingChildren(const PaintLayer* ancestorLayer, LayoutRect& result) |
| { |
| if (ancestorLayer->reflectionInfo() && !ancestorLayer->reflectionInfo()->reflectionLayer()->hasCompositedLayerMapping()) |
| result.unite(ancestorLayer->reflectionInfo()->reflectionLayer()->boundingBoxForCompositing(ancestorLayer)); |
| |
| ASSERT(ancestorLayer->stackingNode()->isStackingContext() || !ancestorLayer->stackingNode()->hasPositiveZOrderList()); |
| |
| #if ENABLE(ASSERT) |
| LayerListMutationDetector mutationChecker(const_cast<PaintLayer*>(ancestorLayer)->stackingNode()); |
| #endif |
| |
| PaintLayerStackingNodeIterator iterator(*ancestorLayer->stackingNode(), AllChildren); |
| while (PaintLayerStackingNode* node = iterator.next()) { |
| // Here we exclude both directly composited layers and squashing layers |
| // because those Layers don't paint into the graphics layer |
| // for this Layer. For example, the bounds of squashed Layers |
| // will be included in the computation of the appropriate squashing |
| // GraphicsLayer. |
| if (node->layer()->compositingState() != NotComposited) |
| continue; |
| result.unite(node->layer()->boundingBoxForCompositing(ancestorLayer)); |
| } |
| } |
| |
| LayoutRect PaintLayer::physicalBoundingBoxIncludingReflectionAndStackingChildren(const LayoutPoint& offsetFromRoot) const |
| { |
| LayoutRect result = physicalBoundingBox(LayoutPoint()); |
| |
| const_cast<PaintLayer*>(this)->stackingNode()->updateLayerListsIfNeeded(); |
| |
| expandRectForReflectionAndStackingChildren(this, result); |
| |
| result.moveBy(offsetFromRoot); |
| return result; |
| } |
| |
| LayoutRect PaintLayer::boundingBoxForCompositing(const PaintLayer* ancestorLayer, CalculateBoundsOptions options) const |
| { |
| if (!isSelfPaintingLayer()) |
| return LayoutRect(); |
| |
| if (!ancestorLayer) |
| ancestorLayer = this; |
| |
| // FIXME: This could be improved to do a check like hasVisibleNonCompositingDescendantLayers() (bug 92580). |
| if (this != ancestorLayer && !hasVisibleContent() && !hasVisibleDescendant()) |
| return LayoutRect(); |
| |
| // The root layer is always just the size of the document. |
| if (isRootLayer()) |
| return LayoutRect(m_layoutObject->view()->documentRect()); |
| |
| // The layer created for the LayoutFlowThread is just a helper for painting and hit-testing, |
| // and should not contribute to the bounding box. The LayoutMultiColumnSets will contribute |
| // the correct size for the layout content of the multicol container. |
| if (layoutObject()->isLayoutFlowThread()) |
| return LayoutRect(); |
| |
| // If there is a clip applied by an ancestor to this PaintLayer but below or equal to |ancestorLayer|, |
| // use that clip as the bounds rather than the recursive bounding boxes, since the latter may be larger than the |
| // actual size. See https://bugs.webkit.org/show_bug.cgi?id=80372 for examples. |
| LayoutRect result = clipper().localClipRect(ancestorLayer); |
| // TODO(chrishtr): avoid converting to IntRect and back. |
| if (result == LayoutRect(LayoutRect::infiniteIntRect())) { |
| result = physicalBoundingBox(LayoutPoint()); |
| |
| const_cast<PaintLayer*>(this)->stackingNode()->updateLayerListsIfNeeded(); |
| |
| // Reflections are implemented with Layers that hang off of the reflected layer. However, |
| // the reflection layer subtree does not include the subtree of the parent Layer, so |
| // a recursive computation of stacking children yields no results. This breaks cases when there are stacking |
| // children of the parent, that need to be included in reflected composited bounds. |
| // Fix this by including composited bounds of stacking children of the reflected Layer. |
| if (hasCompositedLayerMapping() && parent() && parent()->reflectionInfo() && parent()->reflectionInfo()->reflectionLayer() == this) |
| expandRectForReflectionAndStackingChildren(parent(), result); |
| else |
| expandRectForReflectionAndStackingChildren(this, 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 |
| result.expand(filterOutsets()); |
| } |
| |
| if (transform() && paintsWithTransform(GlobalPaintNormalPhase) && (this != ancestorLayer || options == MaybeIncludeTransformForAncestorLayer)) |
| result = transform()->mapRect(result); |
| |
| if (enclosingPaginationLayer()) { |
| convertFromFlowThreadToVisualBoundingBoxInAncestor(this, ancestorLayer, result); |
| return result; |
| } |
| LayoutPoint delta; |
| convertToLayerCoords(ancestorLayer, delta); |
| result.moveBy(delta); |
| return result; |
| } |
| |
| CompositingState PaintLayer::compositingState() const |
| { |
| ASSERT(isAllowedToQueryCompositingState()); |
| |
| // This is computed procedurally so there is no redundant state variable that |
| // can get out of sync from the real actual compositing state. |
| |
| if (m_groupedMapping) { |
| ASSERT(!m_compositedLayerMapping); |
| return PaintsIntoGroupedBacking; |
| } |
| |
| if (!m_compositedLayerMapping) |
| return NotComposited; |
| |
| return PaintsIntoOwnBacking; |
| } |
| |
| bool PaintLayer::isAllowedToQueryCompositingState() const |
| { |
| if (gCompositingQueryMode == CompositingQueriesAreAllowed || RuntimeEnabledFeatures::slimmingPaintV2Enabled()) |
| return true; |
| return layoutObject()->document().lifecycle().state() >= DocumentLifecycle::InCompositingUpdate; |
| } |
| |
| CompositedLayerMapping* PaintLayer::compositedLayerMapping() const |
| { |
| ASSERT(isAllowedToQueryCompositingState()); |
| return m_compositedLayerMapping.get(); |
| } |
| |
| GraphicsLayer* PaintLayer::graphicsLayerBacking() const |
| { |
| switch (compositingState()) { |
| case NotComposited: |
| return 0; |
| case PaintsIntoGroupedBacking: |
| return groupedMapping()->squashingLayer(); |
| default: |
| return compositedLayerMapping()->mainGraphicsLayer(); |
| } |
| } |
| |
| GraphicsLayer* PaintLayer::graphicsLayerBackingForScrolling() const |
| { |
| switch (compositingState()) { |
| case NotComposited: |
| return 0; |
| case PaintsIntoGroupedBacking: |
| return groupedMapping()->squashingLayer(); |
| default: |
| return compositedLayerMapping()->scrollingContentsLayer() ? compositedLayerMapping()->scrollingContentsLayer() : compositedLayerMapping()->mainGraphicsLayer(); |
| } |
| } |
| |
| void PaintLayer::ensureCompositedLayerMapping() |
| { |
| if (m_compositedLayerMapping) |
| return; |
| |
| m_compositedLayerMapping = adoptPtr(new CompositedLayerMapping(*this)); |
| m_compositedLayerMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); |
| |
| updateOrRemoveFilterEffectBuilder(); |
| } |
| |
| void PaintLayer::clearCompositedLayerMapping(bool layerBeingDestroyed) |
| { |
| if (!layerBeingDestroyed) { |
| // We need to make sure our decendants get a geometry update. In principle, |
| // we could call setNeedsGraphicsLayerUpdate on our children, but that would |
| // require walking the z-order lists to find them. Instead, we over-invalidate |
| // by marking our parent as needing a geometry update. |
| if (PaintLayer* compositingParent = enclosingLayerWithCompositedLayerMapping(ExcludeSelf)) |
| compositingParent->compositedLayerMapping()->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); |
| } |
| |
| m_compositedLayerMapping.clear(); |
| |
| if (!layerBeingDestroyed) |
| updateOrRemoveFilterEffectBuilder(); |
| } |
| |
| void PaintLayer::setGroupedMapping(CompositedLayerMapping* groupedMapping, SetGroupMappingOptions options) |
| { |
| if (groupedMapping == m_groupedMapping) |
| return; |
| |
| if (options == InvalidateLayerAndRemoveFromMapping && m_groupedMapping) { |
| m_groupedMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); |
| m_groupedMapping->removeLayerFromSquashingGraphicsLayer(this); |
| } |
| m_groupedMapping = groupedMapping; |
| ASSERT(!m_groupedMapping || m_groupedMapping->verifyLayerInSquashingVector(this)); |
| if (options == InvalidateLayerAndRemoveFromMapping && m_groupedMapping) |
| m_groupedMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree); |
| } |
| |
| bool PaintLayer::hasCompositedMask() const |
| { |
| return m_compositedLayerMapping && m_compositedLayerMapping->hasMaskLayer(); |
| } |
| |
| bool PaintLayer::hasCompositedClippingMask() const |
| { |
| return m_compositedLayerMapping && m_compositedLayerMapping->hasChildClippingMaskLayer(); |
| } |
| |
| bool PaintLayer::paintsWithTransform(GlobalPaintFlags globalPaintFlags) const |
| { |
| return (transform() || layoutObject()->style()->position() == FixedPosition) && ((globalPaintFlags & GlobalPaintFlattenCompositingLayers) || compositingState() != PaintsIntoOwnBacking); |
| } |
| |
| bool PaintLayer::backgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const |
| { |
| if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant()) |
| return false; |
| |
| if (paintsWithTransparency(GlobalPaintNormalPhase)) |
| return false; |
| |
| // We can't use hasVisibleContent(), because that will be true if our layoutObject is hidden, but some child |
| // is visible and that child doesn't cover the entire rect. |
| if (layoutObject()->style()->visibility() != VISIBLE) |
| return false; |
| |
| if (paintsWithFilters() && layoutObject()->style()->filter().hasFilterThatAffectsOpacity()) |
| return false; |
| |
| // FIXME: Handle simple transforms. |
| if (paintsWithTransform(GlobalPaintNormalPhase)) |
| return false; |
| |
| // FIXME: Remove this check. |
| // This function should not be called when layer-lists are dirty. |
| // It is somehow getting triggered during style update. |
| if (m_stackingNode->zOrderListsDirty()) |
| return false; |
| |
| // FIXME: We currently only check the immediate layoutObject, |
| // which will miss many cases. |
| if (layoutObject()->backgroundIsKnownToBeOpaqueInRect(localRect)) |
| return true; |
| |
| // We can't consult child layers if we clip, since they might cover |
| // parts of the rect that are clipped out. |
| if (layoutObject()->hasOverflowClip()) |
| return false; |
| |
| return childBackgroundIsKnownToBeOpaqueInRect(localRect); |
| } |
| |
| bool PaintLayer::childBackgroundIsKnownToBeOpaqueInRect(const LayoutRect& localRect) const |
| { |
| PaintLayerStackingNodeReverseIterator revertseIterator(*m_stackingNode, PositiveZOrderChildren | NormalFlowChildren | NegativeZOrderChildren); |
| while (PaintLayerStackingNode* child = revertseIterator.next()) { |
| const PaintLayer* childLayer = child->layer(); |
| // Stop at composited paint boundaries. |
| if (childLayer->isPaintInvalidationContainer()) |
| continue; |
| |
| if (!childLayer->canUseConvertToLayerCoords()) |
| continue; |
| |
| LayoutPoint childOffset; |
| LayoutRect childLocalRect(localRect); |
| childLayer->convertToLayerCoords(this, childOffset); |
| childLocalRect.moveBy(-childOffset); |
| |
| if (childLayer->backgroundIsKnownToBeOpaqueInRect(childLocalRect)) |
| return true; |
| } |
| return false; |
| } |
| |
| bool PaintLayer::shouldBeSelfPaintingLayer() const |
| { |
| if (layoutObject()->isLayoutPart() && toLayoutPart(layoutObject())->requiresAcceleratedCompositing()) |
| return true; |
| return m_layerType == NormalPaintLayer |
| || (m_scrollableArea && m_scrollableArea->hasOverlayScrollbars()) |
| || needsCompositedScrolling(); |
| } |
| |
| void PaintLayer::updateSelfPaintingLayer() |
| { |
| bool isSelfPaintingLayer = shouldBeSelfPaintingLayer(); |
| if (this->isSelfPaintingLayer() == isSelfPaintingLayer) |
| return; |
| |
| m_isSelfPaintingLayer = isSelfPaintingLayer; |
| |
| if (parent()) |
| parent()->dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); |
| } |
| |
| bool PaintLayer::hasNonEmptyChildLayoutObjects() const |
| { |
| // Some HTML can cause whitespace text nodes to have layoutObjects, like: |
| // <div> |
| // <img src=...> |
| // </div> |
| // so test for 0x0 LayoutTexts here |
| for (LayoutObject* child = layoutObject()->slowFirstChild(); child; child = child->nextSibling()) { |
| if (!child->hasLayer()) { |
| if (child->isLayoutInline() || !child->isBox()) |
| return true; |
| |
| if (toLayoutBox(child)->size().width() > 0 || toLayoutBox(child)->size().height() > 0) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PaintLayer::hasBoxDecorationsOrBackground() const |
| { |
| return layoutObject()->style()->hasBoxDecorations() || layoutObject()->style()->hasBackground(); |
| } |
| |
| bool PaintLayer::hasVisibleBoxDecorations() const |
| { |
| if (!hasVisibleContent()) |
| return false; |
| |
| return hasBoxDecorationsOrBackground() || hasOverflowControls(); |
| } |
| |
| void PaintLayer::updateFilters(const ComputedStyle* oldStyle, const ComputedStyle& newStyle) |
| { |
| if (!newStyle.hasFilter() && (!oldStyle || !oldStyle->hasFilter())) |
| return; |
| |
| updateOrRemoveFilterClients(); |
| updateOrRemoveFilterEffectBuilder(); |
| } |
| |
| bool PaintLayer::attemptDirectCompositingUpdate(StyleDifference diff, const ComputedStyle* oldStyle) |
| { |
| CompositingReasons oldPotentialCompositingReasonsFromStyle = m_potentialCompositingReasonsFromStyle; |
| compositor()->updatePotentialCompositingReasonsFromStyle(this); |
| |
| // This function implements an optimization for transforms and opacity. |
| // A common pattern is for a touchmove handler to update the transform |
| // and/or an opacity of an element every frame while the user moves their |
| // finger across the screen. The conditions below recognize when the |
| // compositing state is set up to receive a direct transform or opacity |
| // update. |
| |
| if (!diff.hasAtMostPropertySpecificDifferences(StyleDifference::TransformChanged | StyleDifference::OpacityChanged)) |
| return false; |
| // The potentialCompositingReasonsFromStyle could have changed without |
| // a corresponding StyleDifference if an animation started or ended. |
| if (m_potentialCompositingReasonsFromStyle != oldPotentialCompositingReasonsFromStyle) |
| return false; |
| // We could add support for reflections if we updated the transform on |
| // the reflection layers. |
| if (layoutObject()->hasReflection()) |
| return false; |
| // If we're unwinding a scheduleSVGFilterLayerUpdateHack(), then we can't |
| // perform a direct compositing update because the filters code is going |
| // to produce different output this time around. We can remove this code |
| // once we fix the chicken/egg bugs in the filters code and delete the |
| // scheduleSVGFilterLayerUpdateHack(). |
| if (layoutObject()->node() && layoutObject()->node()->svgFilterNeedsLayerUpdate()) |
| return false; |
| if (!m_compositedLayerMapping) |
| return false; |
| |
| // To cut off almost all the work in the compositing update for |
| // this case, we treat inline transforms has having assumed overlap |
| // (similar to how we treat animated transforms). Notice that we read |
| // CompositingReasonInlineTransform from the m_compositingReasons, which |
| // means that the inline transform actually triggered assumed overlap in |
| // the overlap map. |
| if (diff.transformChanged() && !(m_compositingReasons & CompositingReasonInlineTransform)) |
| return false; |
| |
| // We composite transparent Layers differently from non-transparent |
| // Layers even when the non-transparent Layers are already a |
| // stacking context. |
| if (diff.opacityChanged() && m_layoutObject->style()->hasOpacity() != oldStyle->hasOpacity()) |
| return false; |
| |
| // Changes in pointer-events affect hit test visibility of the scrollable |
| // area and its |m_scrollsOverflow| value which determines if the layer |
| // requires composited scrolling or not. |
| if (m_scrollableArea && m_layoutObject->style()->pointerEvents() != oldStyle->pointerEvents()) |
| return false; |
| |
| updateTransform(oldStyle, layoutObject()->styleRef()); |
| |
| // FIXME: Consider introducing a smaller graphics layer update scope |
| // that just handles transforms and opacity. GraphicsLayerUpdateLocal |
| // will also program bounds, clips, and many other properties that could |
| // not possibly have changed. |
| m_compositedLayerMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateLocal); |
| compositor()->setNeedsCompositingUpdate(CompositingUpdateAfterGeometryChange); |
| |
| if (m_scrollableArea) |
| m_scrollableArea->updateAfterStyleChange(oldStyle); |
| |
| return true; |
| } |
| |
| void PaintLayer::styleChanged(StyleDifference diff, const ComputedStyle* oldStyle) |
| { |
| if (attemptDirectCompositingUpdate(diff, oldStyle)) |
| return; |
| |
| m_stackingNode->updateIsTreatedAsStackingContext(); |
| 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(); |
| |
| if (!oldStyle || !layoutObject()->style()->reflectionDataEquivalent(oldStyle)) { |
| ASSERT(!oldStyle || diff.needsFullLayout()); |
| updateReflectionInfo(oldStyle); |
| } |
| |
| updateDescendantDependentFlags(); |
| |
| updateTransform(oldStyle, layoutObject()->styleRef()); |
| updateFilters(oldStyle, layoutObject()->styleRef()); |
| |
| setNeedsCompositingInputsUpdate(); |
| } |
| |
| bool PaintLayer::scrollsOverflow() const |
| { |
| if (PaintLayerScrollableArea* scrollableArea = this->scrollableArea()) |
| return scrollableArea->scrollsOverflow(); |
| |
| return false; |
| } |
| |
| namespace { |
| |
| FilterOperations computeFilterOperationsHandleReferenceFilters(const FilterOperations& filters, float effectiveZoom, Node* enclosingNode) |
| { |
| if (filters.hasReferenceFilter()) { |
| for (size_t i = 0; i < filters.size(); ++i) { |
| FilterOperation* filterOperation = filters.operations().at(i).get(); |
| if (filterOperation->type() != FilterOperation::REFERENCE) |
| continue; |
| ReferenceFilterOperation& referenceOperation = toReferenceFilterOperation(*filterOperation); |
| // FIXME: Cache the Filter if it didn't change. |
| RefPtrWillBeRawPtr<Filter> referenceFilter = ReferenceFilterBuilder::build(effectiveZoom, toElement(enclosingNode), nullptr, referenceOperation); |
| referenceOperation.setFilter(referenceFilter.release()); |
| } |
| } |
| |
| return filters; |
| } |
| |
| } // unnamed namespace |
| |
| FilterOperations PaintLayer::computeFilterOperations(const ComputedStyle& style) const |
| { |
| return computeFilterOperationsHandleReferenceFilters(style.filter(), style.effectiveZoom(), enclosingNode()); |
| } |
| |
| FilterOperations PaintLayer::computeBackdropFilterOperations(const ComputedStyle& style) const |
| { |
| return computeFilterOperationsHandleReferenceFilters(style.backdropFilter(), style.effectiveZoom(), enclosingNode()); |
| } |
| |
| void PaintLayer::updateOrRemoveFilterClients() |
| { |
| if (!hasFilter()) { |
| removeFilterInfoIfNeeded(); |
| return; |
| } |
| |
| if (layoutObject()->style()->filter().hasReferenceFilter()) |
| ensureFilterInfo()->updateReferenceFilterClients(layoutObject()->style()->filter()); |
| else if (hasFilterInfo()) |
| filterInfo()->removeReferenceFilterClients(); |
| } |
| |
| FilterEffectBuilder* PaintLayer::updateFilterEffectBuilder() const |
| { |
| // TODO(chrishtr): ensure (and assert) that compositing is clean here. |
| |
| if (!paintsWithFilters()) |
| return nullptr; |
| |
| PaintLayerFilterInfo* filterInfo = this->filterInfo(); |
| |
| // Should have been added by updateOrRemoveFilterEffectBuilder(). |
| ASSERT(filterInfo); |
| |
| if (filterInfo->builder()) |
| return filterInfo->builder(); |
| |
| filterInfo->setBuilder(FilterEffectBuilder::create()); |
| |
| float zoom = layoutObject()->style() ? layoutObject()->style()->effectiveZoom() : 1.0f; |
| if (!filterInfo->builder()->build(toElement(enclosingNode()), computeFilterOperations(layoutObject()->styleRef()), zoom)) |
| filterInfo->setBuilder(nullptr); |
| |
| return filterInfo->builder(); |
| } |
| |
| FilterEffect* PaintLayer::lastFilterEffect() const |
| { |
| FilterEffectBuilder* builder = updateFilterEffectBuilder(); |
| if (!builder) |
| return nullptr; |
| return builder->lastEffect().get(); |
| } |
| |
| FilterOutsets PaintLayer::filterOutsets() const |
| { |
| if (!layoutObject()->hasFilter()) |
| return FilterOutsets(); |
| |
| // Ensure the filter-chain is refreshed wrt reference filters. |
| updateFilterEffectBuilder(); |
| return layoutObject()->style()->filter().outsets(); |
| } |
| |
| void PaintLayer::updateOrRemoveFilterEffectBuilder() |
| { |
| // FilterEffectBuilder is only used to render the filters in software mode, |
| // so we always need to run updateOrRemoveFilterEffectBuilder 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 (PaintLayerFilterInfo* filterInfo = this->filterInfo()) |
| filterInfo->setBuilder(nullptr); |
| |
| return; |
| } |
| |
| ensureFilterInfo()->setBuilder(nullptr); |
| } |
| |
| void PaintLayer::filterNeedsPaintInvalidation() |
| { |
| { |
| DeprecatedScheduleStyleRecalcDuringLayout marker(layoutObject()->document().lifecycle()); |
| // It's possible for scheduleSVGFilterLayerUpdateHack to schedule a style recalc, which |
| // is a problem because this function can be called while performing layout. |
| // Presumably this represents an illegal data flow of layout or compositing |
| // information into the style system. |
| toElement(layoutObject()->node())->scheduleSVGFilterLayerUpdateHack(); |
| } |
| |
| layoutObject()->setShouldDoFullPaintInvalidation(); |
| } |
| |
| void PaintLayer::addLayerHitTestRects(LayerHitTestRects& rects) const |
| { |
| computeSelfHitTestRects(rects); |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->addLayerHitTestRects(rects); |
| } |
| |
| void PaintLayer::computeSelfHitTestRects(LayerHitTestRects& rects) const |
| { |
| if (!size().isEmpty()) { |
| Vector<LayoutRect> rect; |
| |
| if (layoutBox() && layoutBox()->scrollsOverflow()) { |
| // For scrolling layers, rects are taken to be in the space of the contents. |
| // We need to include the bounding box of the layer in the space of its parent |
| // (eg. for border / scroll bars) and if it's composited then the entire contents |
| // as well as they may be on another composited layer. Skip reporting contents |
| // for non-composited layers as they'll get projected to the same layer as the |
| // bounding box. |
| if (compositingState() != NotComposited) |
| rect.append(m_scrollableArea->overflowRect()); |
| |
| rects.set(this, rect); |
| if (const PaintLayer* parentLayer = parent()) { |
| LayerHitTestRects::iterator iter = rects.find(parentLayer); |
| if (iter == rects.end()) { |
| rects.add(parentLayer, Vector<LayoutRect>()).storedValue->value.append(physicalBoundingBox(parentLayer)); |
| } else { |
| iter->value.append(physicalBoundingBox(parentLayer)); |
| } |
| } |
| } else { |
| rect.append(logicalBoundingBox()); |
| rects.set(this, rect); |
| } |
| } |
| } |
| |
| void PaintLayer::setNeedsRepaint() |
| { |
| m_needsRepaint = true; |
| |
| // Do this unconditionally to ensure container chain is marked when compositing status of the layer changes. |
| markCompositingContainerChainForNeedsRepaint(); |
| } |
| |
| void PaintLayer::markCompositingContainerChainForNeedsRepaint() |
| { |
| // Need to access compositingState(). We've ensured correct flag setting when compositingState() changes. |
| DisableCompositingQueryAsserts disabler; |
| |
| PaintLayer* layer = this; |
| while (true) { |
| if (layer->compositingState() == PaintsIntoOwnBacking) |
| return; |
| if (CompositedLayerMapping* groupedMapping = layer->groupedMapping()) { |
| groupedMapping->owningLayer().setNeedsRepaint(); |
| return; |
| } |
| |
| PaintLayer* container = layer->compositingContainer(); |
| if (!container) { |
| LayoutObject* owner = layer->layoutObject()->frame()->ownerLayoutObject(); |
| if (!owner) |
| break; |
| container = owner->enclosingLayer(); |
| } |
| if (container->m_needsRepaint) |
| break; |
| container->m_needsRepaint = true; |
| layer = container; |
| } |
| } |
| |
| void PaintLayer::clearNeedsRepaintRecursively() |
| { |
| for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) |
| child->clearNeedsRepaintRecursively(); |
| m_needsRepaint = false; |
| } |
| |
| PaintTiming* PaintLayer::paintTiming() |
| { |
| if (Node* node = layoutObject()->node()) |
| return &PaintTiming::from(node->document()); |
| return nullptr; |
| } |
| |
| DisableCompositingQueryAsserts::DisableCompositingQueryAsserts() |
| : m_disabler(gCompositingQueryMode, CompositingQueriesAreAllowed) { } |
| |
| } // namespace blink |
| |
| #ifndef NDEBUG |
| // FIXME: Rename? |
| void showLayerTree(const blink::PaintLayer* layer) |
| { |
| if (!layer) { |
| fprintf(stderr, "Cannot showLayerTree. Root is (nil)\n"); |
| return; |
| } |
| |
| if (blink::LocalFrame* frame = layer->layoutObject()->frame()) { |
| WTF::String output = externalRepresentation(frame, blink::LayoutAsTextShowAllLayers | blink::LayoutAsTextShowLayerNesting | blink::LayoutAsTextShowCompositedLayers | blink::LayoutAsTextShowAddresses | blink::LayoutAsTextShowIDAndClass | blink::LayoutAsTextDontUpdateLayout | blink::LayoutAsTextShowLayoutState); |
| fprintf(stderr, "%s\n", output.utf8().data()); |
| } |
| } |
| |
| void showLayerTree(const blink::LayoutObject* layoutObject) |
| { |
| if (!layoutObject) { |
| fprintf(stderr, "Cannot showLayerTree. Root is (nil)\n"); |
| return; |
| } |
| showLayerTree(layoutObject->enclosingLayer()); |
| } |
| #endif |