blob: a7ed7bdb3a8a6fcdf93de359267a0189b4915a90 [file] [log] [blame]
/*
* 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 "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/frame/Settings.h"
#include "core/html/HTMLFrameElement.h"
#include "core/layout/FragmentainerIterator.h"
#include "core/layout/HitTestRequest.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/HitTestingTransformState.h"
#include "core/layout/LayoutFlowThread.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutPart.h"
#include "core/layout/LayoutTreeAsText.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutPartItem.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/compositing/CompositedLayerMapping.h"
#include "core/layout/compositing/PaintLayerCompositor.h"
#include "core/layout/svg/LayoutSVGResourceClipper.h"
#include "core/layout/svg/LayoutSVGRoot.h"
#include "core/page/Page.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/paint/BoxReflectionUtils.h"
#include "core/paint/FilterEffectBuilder.h"
#include "core/paint/ObjectPaintInvalidator.h"
#include "core/paint/PaintTiming.h"
#include "platform/LengthFunctions.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/FloatPoint3D.h"
#include "platform/geometry/FloatRect.h"
#include "platform/geometry/TransformState.h"
#include "platform/graphics/CompositorFilterOperations.h"
#include "platform/graphics/filters/Filter.h"
#include "platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "platform/tracing/TraceEvent.h"
#include "platform/transforms/ScaleTransformOperation.h"
#include "platform/transforms/TransformationMatrix.h"
#include "platform/transforms/TranslateTransformOperation.h"
#include "wtf/PtrUtil.h"
#include "wtf/StdLibExtras.h"
#include "wtf/allocator/Partitions.h"
#include "wtf/text/CString.h"
namespace blink {
namespace {
static CompositingQueryMode gCompositingQueryMode =
CompositingQueriesAreOnlyAllowedInCertainDocumentLifecyclePhases;
struct SameSizeAsPaintLayer : DisplayItemClient {
int bitFields;
void* pointers[10];
LayoutUnit layoutUnits[4];
IntSize size;
Persistent<PaintLayerScrollableArea> scrollableArea;
struct {
IntRect rect, rect2;
void* pointers[2];
} ancestorCompositingInputs;
struct {
IntSize size;
void* pointer;
LayoutRect rect;
} previousPaintStatus;
};
static_assert(sizeof(PaintLayer) == sizeof(SameSizeAsPaintLayer),
"PaintLayer should stay small");
} // namespace
using namespace HTMLNames;
PaintLayerRareData::PaintLayerRareData()
: enclosingPaginationLayer(nullptr),
potentialCompositingReasonsFromStyle(CompositingReasonNone),
compositingReasons(CompositingReasonNone),
squashingDisallowedReasons(SquashingDisallowedReasonsNone),
groupedMapping(nullptr) {}
PaintLayerRareData::~PaintLayerRareData() {}
PaintLayer::PaintLayer(LayoutBoxModelObject* layoutObject)
: m_hasSelfPaintingLayerDescendant(false),
m_hasSelfPaintingLayerDescendantDirty(false),
m_isRootLayer(layoutObject->isLayoutView()),
m_isVisibleContentDirty(true),
m_hasVisibleContent(false),
m_isVisibleDescendantDirty(false),
m_hasVisibleDescendant(false),
#if DCHECK_IS_ON()
m_needsPositionUpdate(true),
#endif
m_is3DTransformedDescendantDirty(true),
m_has3DTransformedDescendant(false),
m_containsDirtyOverlayScrollbars(false),
m_needsAncestorDependentCompositingInputsUpdate(true),
m_needsDescendantDependentCompositingInputsUpdate(true),
m_childNeedsCompositingInputsUpdate(true),
m_hasCompositingDescendant(false),
m_isAllScrollingContentComposited(false),
m_shouldIsolateCompositedDescendants(false),
m_lostGroupedMapping(false),
m_needsRepaint(false),
m_previousPaintResult(PaintLayerPainter::FullyPainted),
m_needsPaintPhaseDescendantOutlines(false),
m_previousPaintPhaseDescendantOutlinesWasEmpty(false),
m_needsPaintPhaseFloat(false),
m_previousPaintPhaseFloatWasEmpty(false),
m_needsPaintPhaseDescendantBlockBackgrounds(false),
m_previousPaintPhaseDescendantBlockBackgroundsWasEmpty(false),
m_hasDescendantWithClipPath(false),
m_hasNonIsolatedDescendantWithBlendMode(false),
m_hasAncestorWithClipPath(false),
m_hasRootScrollerAsDescendant(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_ancestorOverflowLayer(nullptr) {
updateStackingNode();
m_isSelfPaintingLayer = shouldBeSelfPaintingLayer();
if (!layoutObject->slowFirstChild() && layoutObject->style()) {
m_isVisibleContentDirty = false;
m_hasVisibleContent =
layoutObject->style()->visibility() == EVisibility::Visible;
}
updateScrollableArea();
}
PaintLayer::~PaintLayer() {
if (m_rareData && m_rareData->filterInfo)
m_rareData->filterInfo->clearLayer();
if (layoutObject()->frame() && layoutObject()->frame()->page()) {
if (ScrollingCoordinator* scrollingCoordinator =
layoutObject()->frame()->page()->scrollingCoordinator())
scrollingCoordinator->willDestroyLayer(this);
}
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_scrollableArea)
m_scrollableArea->dispose();
}
String PaintLayer::debugName() const {
return layoutObject()->debugName();
}
LayoutRect PaintLayer::visualRect() const {
return m_layoutObject->visualRect();
}
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 (CompositedLayerMapping* compositedLayerMapping =
this->compositedLayerMapping())
compositedLayerMapping->contentChanged(changeType);
}
bool PaintLayer::paintsWithFilters() const {
if (!layoutObject()->hasFilterInducingProperty())
return false;
// https://code.google.com/p/chromium/issues/detail?id=343759
DisableCompositingQueryAsserts disabler;
return !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 !compositedLayerMapping() ||
compositingState() != PaintsIntoOwnBacking;
}
LayoutSize PaintLayer::subpixelAccumulation() const {
return m_rareData ? m_rareData->subpixelAccumulation : LayoutSize();
}
void PaintLayer::setSubpixelAccumulation(const LayoutSize& size) {
if (m_rareData || !size.isZero())
ensureRareData().subpixelAccumulation = size;
}
void PaintLayer::updateLayerPositionsAfterLayout() {
TRACE_EVENT0("blink,benchmark",
"PaintLayer::updateLayerPositionsAfterLayout");
clipper().clearClipRectsIncludingDescendants();
updateLayerPositionRecursive();
{
// FIXME: Remove incremental compositing updates after fixing the
// chicken/egg issues, https://crbug.com/343756
DisableCompositingQueryAsserts disabler;
updatePaginationRecursive(enclosingPaginationLayer());
}
}
void PaintLayer::updateLayerPositionRecursive() {
updateLayerPosition();
// 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 {
DCHECK(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()) {
DCHECK(!parent() || parent()->m_hasSelfPaintingLayerDescendantDirty ||
parent()->m_hasSelfPaintingLayerDescendant);
break;
}
}
}
bool PaintLayer::scrollsWithViewport() const {
return (layoutObject()->style()->position() == FixedPosition &&
layoutObject()->containerForFixedPosition() ==
layoutObject()->view()) ||
(layoutObject()->style()->position() == StickyPosition &&
!ancestorScrollingLayer());
}
bool PaintLayer::scrollsWithRespectTo(const PaintLayer* other) const {
if (scrollsWithViewport() != other->scrollsWithViewport())
return true;
return ancestorScrollingLayer() != other->ancestorScrollingLayer();
}
void PaintLayer::updateLayerPositionsAfterOverflowScroll(
const DoubleSize& scrollDelta) {
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 (TransformationMatrix* transform = this->transform()) {
LayoutBox* box = layoutBox();
DCHECK(box);
transform->makeIdentity();
box->style()->applyTransform(
*transform, box->size(), ComputedStyle::IncludeTransformOrigin,
ComputedStyle::IncludeMotionPath,
ComputedStyle::IncludeIndependentTransformProperties);
makeMatrixRenderable(*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 = transform();
if (hasTransform != hadTransform) {
if (hasTransform)
ensureRareData().transform = TransformationMatrix::create();
else
m_rareData->transform.reset();
// PaintLayers with transforms act as clip rects roots, so clear the cached
// clip rects here.
clipper().clearClipRectsIncludingDescendants();
} else if (hasTransform) {
clipper().clearClipRectsIncludingDescendants(AbsoluteClipRects);
}
updateTransformationMatrix();
if (had3DTransform != has3DTransform())
dirty3DTransformedDescendantStatus();
if (FrameView* frameView = layoutObject()->document().view())
frameView->setNeedsUpdateWidgetGeometries();
}
static PaintLayer* enclosingLayerForContainingBlock(PaintLayer* layer) {
if (LayoutObject* containingBlock = layer->layoutObject()->containingBlock())
return containingBlock->enclosingLayer();
return 0;
}
static const PaintLayer* enclosingLayerForContainingBlock(
const PaintLayer* layer) {
if (const 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;
}
const PaintLayer* PaintLayer::renderingContextRoot() const {
const PaintLayer* renderingContext = 0;
if (shouldPreserve3D())
renderingContext = this;
for (const PaintLayer* current = enclosingLayerForContainingBlock(this);
current && current->shouldPreserve3D();
current = enclosingLayerForContainingBlock(current))
renderingContext = current;
return renderingContext;
}
TransformationMatrix PaintLayer::currentTransform() const {
if (TransformationMatrix* transform = this->transform())
return *transform;
return TransformationMatrix();
}
TransformationMatrix PaintLayer::renderableTransform(
GlobalPaintFlags globalPaintFlags) const {
TransformationMatrix* transform = this->transform();
if (!transform)
return TransformationMatrix();
if (globalPaintFlags & GlobalPaintFlattenCompositingLayers) {
TransformationMatrix matrix = *transform;
makeMatrixRenderable(matrix, false /* flatten 3d */);
return matrix;
}
return *transform;
}
void PaintLayer::convertFromFlowThreadToVisualBoundingBoxInAncestor(
const PaintLayer* ancestorLayer,
LayoutRect& rect) const {
PaintLayer* paginationLayer = enclosingPaginationLayer();
DCHECK(paginationLayer);
LayoutFlowThread* flowThread =
toLayoutFlowThread(paginationLayer->layoutObject());
// First make the flow thread rectangle relative to the flow thread, not to
// |layer|.
LayoutPoint offsetWithinPaginationLayer;
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) {
if (m_rareData)
m_rareData->enclosingPaginationLayer = nullptr;
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())
ensureRareData().enclosingPaginationLayer = containingFlowThread->layer();
}
for (PaintLayer* child = firstChild(); child; child = child->nextSibling())
child->updatePaginationRecursive(needsPaginationUpdate);
}
void PaintLayer::clearPaginationRecursive() {
if (m_rareData)
m_rareData->enclosingPaginationLayer = nullptr;
for (PaintLayer* child = firstChild(); child; child = child->nextSibling())
child->clearPaginationRecursive();
}
void PaintLayer::mapPointInPaintInvalidationContainerToBacking(
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.localToAncestorPoint(point,
transformedAncestor);
point.moveBy(-paintInvalidationLayer->groupedMapping()
->squashingOffsetFromTransformedAncestor());
}
void PaintLayer::mapRectInPaintInvalidationContainerToBacking(
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
.localToAncestorQuad(FloatRect(rect), transformedAncestor)
.boundingBox());
rect.moveBy(-paintInvalidationLayer->groupedMapping()
->squashingOffsetFromTransformedAncestor());
}
void PaintLayer::mapRectToPaintInvalidationBacking(
const LayoutObject& layoutObject,
const LayoutBoxModelObject& paintInvalidationContainer,
LayoutRect& rect) {
if (!paintInvalidationContainer.layer()->groupedMapping()) {
layoutObject.mapToVisualRectInAncestorSpace(&paintInvalidationContainer,
rect);
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.mapToVisualRectInAncestorSpace(&paintInvalidationContainer,
rect);
mapRectInPaintInvalidationContainerToBacking(paintInvalidationContainer,
rect);
}
void PaintLayer::dirtyVisibleContentStatus() {
compositor()->setNeedsUpdateDescendantDependentFlags();
m_isVisibleContentDirty = 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_isVisibleContentDirty)
return;
if (hasVisibleContent() == (visibility == EVisibility::Visible))
return;
dirtyVisibleContentStatus();
}
void PaintLayer::dirtyAncestorChainVisibleDescendantStatus() {
compositor()->setNeedsUpdateDescendantDependentFlags();
for (PaintLayer* layer = this; layer; layer = layer->parent()) {
if (layer->m_isVisibleDescendantDirty)
break;
layer->m_isVisibleDescendantDirty = 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_isAllScrollingContentComposited = true;
for (LayoutObject* r = layoutObject()->slowFirstChild(); r;
r = r->nextSibling()) {
if (!r->hasLayer()) {
m_isAllScrollingContentComposited = false;
return;
}
}
for (PaintLayer* child = firstChild(); child; child = child->nextSibling()) {
if (child->compositingState() == NotComposited) {
m_isAllScrollingContentComposited = false;
return;
} else if (!child->stackingNode()->isStackingContext()) {
// If the child is composited, but not a stacking context, it may paint
// negative z-index descendants into an ancestor's GraphicsLayer.
m_isAllScrollingContentComposited = false;
return;
}
}
}
void PaintLayer::updateDescendantDependentFlags() {
if (m_isVisibleDescendantDirty) {
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_isVisibleDescendantDirty = false;
}
if (m_isVisibleContentDirty) {
bool previouslyHasVisibleContent = m_hasVisibleContent;
if (layoutObject()->style()->visibility() == EVisibility::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() == EVisibility::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_isVisibleContentDirty = 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_is3DTransformedDescendantDirty = 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_is3DTransformedDescendantDirty = true;
stackingNode = stackingNode->ancestorStackingContextNode();
}
}
// Return true if this layer or any preserve-3d descendants have 3d.
bool PaintLayer::update3DTransformedDescendantStatus() {
if (m_is3DTransformedDescendantDirty) {
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_is3DTransformedDescendantDirty = 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();
}
void PaintLayer::updateLayerPosition() {
LayoutPoint localPoint;
if (layoutObject()->isInline() && layoutObject()->isLayoutInline()) {
LayoutInline* inlineFlow = toLayoutInline(layoutObject());
IntRect lineBox = enclosingIntRect(inlineFlow->linesBoundingBox());
m_size = lineBox.size();
} 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* containingLayer =
layoutObject()->isOutOfFlowPositioned()
? containingLayerForOutOfFlowPositioned()
: nullptr) {
// For positioned layers, we subtract out the enclosing positioned layer's
// scroll offset.
if (containingLayer->layoutObject()->hasOverflowClip()) {
IntSize offset = containingLayer->layoutBox()->scrolledContentOffset();
localPoint -= offset;
}
if (containingLayer->layoutObject()->isInFlowPositioned() &&
containingLayer->layoutObject()->isLayoutInline()) {
LayoutSize offset =
toLayoutInline(containingLayer->layoutObject())
->offsetForInFlowPositionedInline(*toLayoutBox(layoutObject()));
localPoint += offset;
}
} else if (parent() && parent()->layoutObject()->hasOverflowClip()) {
IntSize scrollOffset = parent()->layoutBox()->scrolledContentOffset();
localPoint -= scrollOffset;
}
if (layoutObject()->isInFlowPositioned()) {
LayoutSize newOffset = layoutObject()->offsetForInFlowPosition();
if (m_rareData || !newOffset.isZero())
ensureRareData().offsetForInFlowPosition = newOffset;
localPoint.move(newOffset);
} else if (m_rareData) {
m_rareData->offsetForInFlowPosition = LayoutSize();
}
if (m_location != localPoint) {
setNeedsRepaint();
}
m_location = localPoint;
#if DCHECK_IS_ON()
m_needsPositionUpdate = false;
#endif
}
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()));
}
PaintLayer* PaintLayer::containingLayerForOutOfFlowPositioned(
const PaintLayer* ancestor,
bool* skippedAncestor) const {
// If we have specified an ancestor, surely the caller needs to know whether
// we skipped it.
DCHECK(!ancestor || skippedAncestor);
if (skippedAncestor)
*skippedAncestor = false;
if (layoutObject()->style()->position() == FixedPosition) {
PaintLayer* curr = parent();
while (curr && !curr->layoutObject()->canContainFixedPositionObjects()) {
if (skippedAncestor && curr == ancestor)
*skippedAncestor = true;
curr = curr->parent();
}
return curr;
}
PaintLayer* curr = parent();
while (curr && !curr->layoutObject()->canContainAbsolutePositionObjects()) {
if (skippedAncestor && curr == ancestor)
*skippedAncestor = true;
curr = curr->parent();
}
return curr;
}
PaintLayer* PaintLayer::enclosingTransformedAncestor() const {
PaintLayer* curr = parent();
while (curr && !curr->isRootLayer() && !curr->transform())
curr = curr->parent();
return curr;
}
LayoutPoint PaintLayer::computeOffsetFromTransformedAncestor() const {
TransformState transformState(TransformState::ApplyTransformDirection,
FloatPoint());
layoutObject()->mapLocalToAncestor(
transformAncestor() ? transformAncestor()->layoutObject() : nullptr,
transformState, 0);
transformState.flatten();
return LayoutPoint(transformState.lastPlanarPoint());
}
PaintLayer* PaintLayer::compositingContainer() const {
if (!stackingNode()->isStacked())
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 {
DCHECK(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) {
CHECK(layer->layoutObject()->frame());
LayoutItem owner = layer->layoutObject()->frame()->ownerLayoutItem();
if (owner.isNull())
break;
layer = owner.enclosingLayer();
}
}
return compositedLayer;
}
PaintLayer* PaintLayer::enclosingLayerForPaintInvalidation() const {
DCHECK(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,
const RareAncestorDependentCompositingInputs& rareCompositingInputs,
bool hasAncestorWithClipPath) {
m_ancestorDependentCompositingInputs = compositingInputs;
if (rareCompositingInputs.isDefault())
m_rareAncestorDependentCompositingInputs.reset();
else
m_rareAncestorDependentCompositingInputs = wrapUnique(
new RareAncestorDependentCompositingInputs(rareCompositingInputs));
m_hasAncestorWithClipPath = hasAncestorWithClipPath;
m_needsAncestorDependentCompositingInputsUpdate = false;
}
void PaintLayer::updateDescendantDependentCompositingInputs(
bool hasDescendantWithClipPath,
bool hasNonIsolatedDescendantWithBlendMode,
bool hasRootScrollerAsDescendant) {
m_hasDescendantWithClipPath = hasDescendantWithClipPath;
m_hasNonIsolatedDescendantWithBlendMode =
hasNonIsolatedDescendantWithBlendMode;
m_hasRootScrollerAsDescendant = hasRootScrollerAsDescendant;
m_needsDescendantDependentCompositingInputsUpdate = false;
}
void PaintLayer::didUpdateCompositingInputs() {
DCHECK(!needsCompositingInputsUpdate());
m_childNeedsCompositingInputsUpdate = false;
if (m_scrollableArea)
m_scrollableArea->updateNeedsCompositedScrolling();
}
bool PaintLayer::hasNonIsolatedDescendantWithBlendMode() const {
DCHECK(!m_needsDescendantDependentCompositingInputsUpdate);
if (m_hasNonIsolatedDescendantWithBlendMode)
return true;
if (layoutObject()->isSVGRoot())
return toLayoutSVGRoot(layoutObject())->hasNonIsolatedBlendingDescendants();
return false;
}
void PaintLayer::setCompositingReasons(CompositingReasons reasons,
CompositingReasons mask) {
CompositingReasons oldReasons =
m_rareData ? m_rareData->compositingReasons : CompositingReasonNone;
if ((oldReasons & mask) == (reasons & mask))
return;
CompositingReasons newReasons = (reasons & mask) | (oldReasons & ~mask);
if (m_rareData || newReasons != CompositingReasonNone)
ensureRareData().compositingReasons = newReasons;
}
void PaintLayer::setSquashingDisallowedReasons(
SquashingDisallowedReasons reasons) {
SquashingDisallowedReasons oldReasons =
m_rareData ? m_rareData->squashingDisallowedReasons
: SquashingDisallowedReasonsNone;
if (oldReasons == reasons)
return;
if (m_rareData || reasons != SquashingDisallowedReasonsNone)
ensureRareData().squashingDisallowedReasons = reasons;
}
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::hasAncestorWithFilterThatMovesPixels() const {
for (const PaintLayer* curr = this; curr; curr = curr->parent()) {
if (curr->hasFilterThatMovesPixels())
return true;
}
return false;
}
static void expandClipRectForDescendants(
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())
clipRect.unite(PaintLayer::transparencyClipBox(
curr, rootLayer, transparencyBehavior,
PaintLayer::DescendantsOfTransparencyClipBox, subPixelAccumulation,
globalPaintFlags));
}
}
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(LayoutPoint());
expandClipRectForDescendants(clipRect, layer, layer, transparencyBehavior,
subPixelAccumulation, globalPaintFlags);
LayoutRect result = enclosingLayoutRect(
transform.mapRect(layer->mapRectForFilter(FloatRect(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->shouldFragmentCompositedBounds(rootLayer)
? layer->fragmentsBoundingBox(rootLayer)
: layer->physicalBoundingBox(rootLayer);
expandClipRectForDescendants(clipRect, layer, rootLayer, transparencyBehavior,
subPixelAccumulation, globalPaintFlags);
// Convert clipRect into local coordinates for mapLayerRectForFilter(), and
// convert back after.
LayoutPoint delta;
layer->convertToLayerCoords(rootLayer, delta);
clipRect.moveBy(-delta);
clipRect = layer->mapLayoutRectForFilter(clipRect);
clipRect.moveBy(delta);
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);
DCHECK(prevSibling != child);
} else {
setFirstChild(child);
}
if (beforeChild) {
beforeChild->setPreviousSibling(child);
child->setNextSibling(beforeChild);
DCHECK(beforeChild != child);
} else {
setLastChild(child);
}
child->m_parent = this;
// The ancestor overflow layer is calculated during compositing inputs update
// and should not be set yet.
DCHECK(!child->ancestorOverflowLayer());
setNeedsCompositingInputsUpdate();
if (!child->stackingNode()->isStacked() &&
!layoutObject()->documentBeingDestroyed())
compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
if (child->stackingNode()->isStacked() || 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()->isStacked() &&
!layoutObject()->documentBeingDestroyed())
compositor()->setNeedsCompositingUpdate(CompositingUpdateRebuildTree);
if (oldChild->stackingNode()->isStacked() || 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() != EVisibility::Visible)
dirtyVisibleContentStatus();
oldChild->setPreviousSibling(0);
oldChild->setNextSibling(0);
oldChild->m_parent = 0;
// Remove any ancestor overflow layers which descended into the removed child.
if (oldChild->ancestorOverflowLayer())
oldChild->removeAncestorOverflowLayer(oldChild->ancestorOverflowLayer());
dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
oldChild->updateDescendantDependentFlags();
if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant)
dirtyAncestorChainVisibleDescendantStatus();
if (oldChild->enclosingPaginationLayer())
oldChild->clearPaginationRecursive();
setNeedsRepaint();
return oldChild;
}
void PaintLayer::removeOnlyThisLayerAfterStyleChange() {
if (!m_parent)
return;
bool didSetPaintInvalidation = false;
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;
ObjectPaintInvalidator(*layoutObject())
.invalidatePaintIncludingNonCompositingDescendants();
layoutObject()
->setShouldDoFullPaintInvalidationIncludingNonCompositingDescendants();
didSetPaintInvalidation = true;
}
}
if (!didSetPaintInvalidation && isSelfPaintingLayer()) {
if (PaintLayer* enclosingSelfPaintingLayer =
m_parent->enclosingSelfPaintingLayer())
enclosingSelfPaintingLayer->mergeNeedsPaintPhaseFlagsFrom(*this);
}
clipper().clearClipRectsIncludingDescendants();
PaintLayer* nextSib = nextSibling();
// 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::insertOnlyThisLayerAfterStyleChange() {
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();
DCHECK(parentLayer);
PaintLayer* beforeChild =
layoutObject()->parent()->findNextLayer(parentLayer, layoutObject());
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);
// If the previous paint invalidation container is not a stacking context and
// this object is stacked content, creating this layer may cause this object
// and its descendants to change paint invalidation container.
bool didSetPaintInvalidation = false;
if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() &&
!layoutObject()->isLayoutView() && layoutObject()->isRooted() &&
layoutObject()->styleRef().isStacked()) {
const LayoutBoxModelObject& previousPaintInvalidationContainer =
layoutObject()->parent()->containerForPaintInvalidation();
if (!previousPaintInvalidationContainer.styleRef().isStackingContext()) {
ObjectPaintInvalidator(*layoutObject())
.invalidatePaintIncludingNonSelfPaintingLayerDescendants(
previousPaintInvalidationContainer);
// Set needsRepaint along the original compositingContainer chain.
layoutObject()->parent()->enclosingLayer()->setNeedsRepaint();
didSetPaintInvalidation = true;
}
}
if (!didSetPaintInvalidation && isSelfPaintingLayer() && m_parent) {
if (PaintLayer* enclosingSelfPaintingLayer =
m_parent->enclosingSelfPaintingLayer())
mergeNeedsPaintPhaseFlagsFrom(*enclosingSelfPaintingLayer);
}
// Clear out all the clip rects.
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) {
DCHECK(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();
location += LayoutSize(absPos.x(), absPos.y());
return ancestorLayer;
}
PaintLayer* parentLayer;
if (position == AbsolutePosition || position == FixedPosition) {
bool foundAncestorFirst;
parentLayer = layer->containingLayerForOutOfFlowPositioned(
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();
DCHECK(toLayoutBlockFlow(multicolContainer)->multiColumnFlowThread());
parentLayer = multicolContainer->layer();
DCHECK(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() {
bool wasSelfPaintingLayer = isSelfPaintingLayer();
updateSelfPaintingLayer();
// If the floating object becomes non-self-painting, so some ancestor should
// paint it; if it becomes self-painting, it should paint itself and no
// ancestor should paint it.
if (wasSelfPaintingLayer != isSelfPaintingLayer() &&
m_layoutObject->isFloating())
LayoutBlockFlow::setAncestorShouldPaintFloatingObject(*layoutBox());
}
void PaintLayer::updateStackingNode() {
DCHECK(!m_stackingNode);
if (requiresStackingNode())
m_stackingNode = wrapUnique(new PaintLayerStackingNode(this));
else
m_stackingNode = nullptr;
}
void PaintLayer::updateScrollableArea() {
DCHECK(!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,
OverlayScrollbarClipBehavior overlayScrollbarClipBehavior,
ShouldRespectOverflowClipType respectOverflowClip,
const LayoutPoint* offsetFromRoot,
const LayoutSize& subPixelAccumulation) {
PaintLayerFragment fragment;
ClipRectsContext clipRectsContext(rootLayer, clipRectsCacheSlot,
overlayScrollbarClipBehavior,
subPixelAccumulation);
if (respectOverflowClip == IgnoreOverflowClip)
clipRectsContext.setIgnoreOverflowClip();
clipper().calculateRects(clipRectsContext, dirtyRect, fragment.layerBounds,
fragment.backgroundRect, fragment.foregroundRect,
offsetFromRoot);
fragments.append(fragment);
}
bool PaintLayer::shouldFragmentCompositedBounds(
const PaintLayer* compositingLayer) const {
// Composited layers may not be fragmented.
return enclosingPaginationLayer() &&
!compositingLayer->enclosingPaginationLayer();
}
void PaintLayer::collectFragments(
PaintLayerFragments& fragments,
const PaintLayer* rootLayer,
const LayoutRect& dirtyRect,
ClipRectsCacheSlot clipRectsCacheSlot,
OverlayScrollbarClipBehavior overlayScrollbarClipBehavior,
ShouldRespectOverflowClipType 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,
overlayScrollbarClipBehavior, respectOverflowClip, offsetFromRoot,
subPixelAccumulation);
return;
}
if (!shouldFragmentCompositedBounds(rootLayer)) {
appendSingleFragmentIgnoringPagination(
fragments, rootLayer, dirtyRect, clipRectsCacheSlot,
overlayScrollbarClipBehavior, 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,
overlayScrollbarClipBehavior);
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());
LayoutFlowThread* enclosingFlowThread =
toLayoutFlowThread(enclosingPaginationLayer()->layoutObject());
// Visual offset from the root layer to the nearest fragmentation context.
LayoutPoint offsetOfPaginationLayerFromRoot;
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);
}
// Make the dirty rect relative to the fragmentation context (multicol
// container, etc.).
LayoutRect dirtyRectInMulticolContainer(dirtyRect);
dirtyRectInMulticolContainer.move(enclosingPaginationLayer()->location() -
offsetOfPaginationLayerFromRoot);
// Slice the layer into fragments. Each fragment needs to be processed (e.g.
// painted) separately. We pass enough information to walk a minimal number of
// fragments based on the pages/columns that intersect the actual dirtyRect as
// well as the pages/columns that intersect our layer's bounding box.
FragmentainerIterator iterator(*enclosingFlowThread,
layerBoundingBoxInFlowThread,
dirtyRectInMulticolContainer);
if (iterator.atEnd())
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,
overlayScrollbarClipBehavior);
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 (; !iterator.atEnd(); iterator.advance()) {
PaintLayerFragment fragment;
fragment.paginationOffset = toLayoutPoint(iterator.paginationOffset());
fragment.paginationClip = iterator.clipRectInFlowThread();
// 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);
// TODO(mstensho): Don't add empty fragments. We've always done that in some
// cases, but there should be no reason to do so. Either filter them out
// here, or, even better: pass a better clip rectangle to the fragmentainer
// iterator, so that we won't end up with empty fragments here.
fragments.append(fragment);
}
}
static inline LayoutRect frameVisibleRect(LayoutObject* layoutObject) {
FrameView* frameView = layoutObject->document().view();
if (!frameView)
return LayoutRect();
return LayoutRect(frameView->visibleContentRect());
}
bool PaintLayer::hitTest(HitTestResult& result) {
DCHECK(isSelfPaintingLayer() || hasSelfPaintingLayerDescendant());
// LayoutView should make sure to update layout before entering hit testing
DCHECK(!layoutObject()->frame()->view()->layoutPending());
DCHECK(!layoutObject()->document().layoutViewItem().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().toInt(), offset.y().toInt(),
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) {
DCHECK(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) {
DCHECK(layoutObject()->document().lifecycle().state() >=
DocumentLifecycle::CompositingClean);
if (!isSelfPaintingLayer() && !hasSelfPaintingLayerDescendant())
return nullptr;
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,
ExcludeOverlayScrollbarSizeForHitTesting));
// Go ahead and test the enclosing clip now.
if (!clipRect.intersects(hitTestLocation))
return nullptr;
}
return hitTestLayerByApplyingTransform(rootLayer, containerLayer, result,
hitTestRect, hitTestLocation,
transformState, zOffset);
}
if (hitTestClippedOutByClipPath(rootLayer, hitTestLocation))
return nullptr;
// 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.
DCHECK(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 nullptr;
}
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,
ExcludeOverlayScrollbarSizeForHitTesting);
else
collectFragments(layerFragments, rootLayer, hitTestRect, clipRectsCacheSlot,
ExcludeOverlayScrollbarSizeForHitTesting);
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 nullptr;
}
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,
ExcludeOverlayScrollbarSizeForHitTesting, 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,
ExcludeOverlayScrollbarSizeForHitTesting))
.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 {
DCHECK(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.
DCHECK(!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.
DCHECK(!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;
}
FloatRect PaintLayer::boxForFilter() const {
return FloatRect(physicalBoundingBoxIncludingStackingChildren(
LayoutPoint(), PaintLayer::CalculateBoundsOptions::
IncludeTransformsAndCompositedChildLayers));
}
LayoutRect PaintLayer::boxForClipPath() const {
if (!layoutObject()->isBox()) {
SECURITY_DCHECK(layoutObject()->isLayoutInline());
const LayoutInline& layoutInline = toLayoutInline(*layoutObject());
// This somewhat convoluted computation matches what Gecko does.
// See crbug.com/641907.
LayoutRect inlineBBox = layoutInline.linesBoundingBox();
const InlineFlowBox* flowBox = layoutInline.firstLineBox();
inlineBBox.setHeight(flowBox ? flowBox->frameRect().height()
: LayoutUnit(0));
return inlineBBox;
}
return toLayoutBox(layoutObject())->borderBoxRect();
}
bool PaintLayer::hitTestClippedOutByClipPath(
PaintLayer* rootLayer,
const HitTestLocation& hitTestLocation) const {
if (!layoutObject()->hasClipPath())
return false;
DCHECK(isSelfPaintingLayer());
DCHECK(rootLayer);
LayoutRect referenceBox(boxForClipPath());
if (enclosingPaginationLayer())
convertFromFlowThreadToVisualBoundingBoxInAncestor(rootLayer, referenceBox);
else
convertToLayerCoords(rootLayer, referenceBox);
FloatPoint point(hitTestLocation.point());
ClipPathOperation* clipPathOperation = layoutObject()->style()->clipPath();
DCHECK(clipPathOperation);
if (clipPathOperation->type() == ClipPathOperation::SHAPE) {
ShapeClipPathOperation* clipPath =
toShapeClipPathOperation(clipPathOperation);
return !clipPath->path(FloatRect(referenceBox)).contains(point);
}
DCHECK_EQ(clipPathOperation->type(), ClipPathOperation::REFERENCE);
ReferenceClipPathOperation* referenceClipPathOperation =
toReferenceClipPathOperation(clipPathOperation);
Element* element = layoutObject()->document().getElementById(
referenceClipPathOperation->fragment());
if (!isSVGClipPathElement(element) || !element->layoutObject())
return false;
LayoutSVGResourceClipper* clipper = toLayoutSVGResourceClipper(
toLayoutSVGResourceContainer(element->layoutObject()));
// If the clipPath is using "userspace on use" units, then the origin of
// the coordinate system is the top-left of the reference box, so adjust
// the point accordingly.
if (clipper->clipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse)
point.moveBy(-referenceBox.location());
return !clipper->hitTestClipContent(FloatRect(referenceBox), point);
}
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();
DCHECK(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(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).
// TODO(trchen): Layer fragmentation is inhibited across compositing boundary.
// Should we return the unfragmented bounds for overlap testing? Or perhaps
// assume fragmented layers always overlap?
return overlapBoundsIncludeChildren()
? boundingBoxForCompositing(this,
NeverIncludeTransformForAncestorLayer)
: fragmentsBoundingBox(this);
}
bool PaintLayer::overlapBoundsIncludeChildren() const {
return hasFilterThatMovesPixels();
}
static void expandRectForStackingChildren(
const PaintLayer* ancestorLayer,
LayoutRect& result,
PaintLayer::CalculateBoundsOptions options) {
DCHECK(ancestorLayer->stackingNode()->isStackingContext() ||
!ancestorLayer->stackingNode()->hasPositiveZOrderList());
#if DCHECK_IS_ON()
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 (options != PaintLayer::CalculateBoundsOptions::
IncludeTransformsAndCompositedChildLayers &&
node->layer()->compositingState() != NotComposited)
continue;
result.unite(
node->layer()->boundingBoxForCompositing(ancestorLayer, options));
}
}
LayoutRect PaintLayer::physicalBoundingBoxIncludingStackingChildren(
const LayoutPoint& offsetFromRoot,
CalculateBoundsOptions options) const {
LayoutRect result = physicalBoundingBox(LayoutPoint());
const_cast<PaintLayer*>(this)->stackingNode()->updateLayerListsIfNeeded();
expandRectForStackingChildren(this, result, options);
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();
// Without composited scrolling, the root layer is the size of the document.
if (isRootLayer() && !needsCompositedScrolling())
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();
expandRectForStackingChildren(this, result, options);
// Only enlarge by the filter outsets if we know the filter is going to be
// rendered in software. Accelerated filters will handle their own outsets.
if (paintsWithFilters())
result = mapLayoutRectForFilter(result);
}
if (transform() && (options == IncludeTransformsAndCompositedChildLayers ||
((paintsWithTransform(GlobalPaintNormalPhase) &&
(this != ancestorLayer ||
options == MaybeIncludeTransformForAncestorLayer)))))
result = transform()->mapRect(result);
if (shouldFragmentCompositedBounds(ancestorLayer)) {
convertFromFlowThreadToVisualBoundingBoxInAncestor(ancestorLayer, result);
return result;
}
LayoutPoint delta;
convertToLayerCoords(ancestorLayer, delta);
result.moveBy(delta);
return result;
}
CompositingState PaintLayer::compositingState() const {
DCHECK(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 (groupedMapping()) {
DCHECK(!compositedLayerMapping());
return PaintsIntoGroupedBacking;
}
if (!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 {
DCHECK(isAllowedToQueryCompositingState());
return m_rareData ? m_rareData->compositedLayerMapping.get() : nullptr;
}
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();
}
}
bool PaintLayer::canPaintBackgroundOntoScrollingContentsLayer() const {
if (isRootLayer() || !scrollsOverflow() ||
!layoutObject()->hasLocalEquivalentBackground())
return false;
m_stackingNode->updateLayerListsIfNeeded();
return !m_stackingNode->hasNegativeZOrderList();
}
void PaintLayer::ensureCompositedLayerMapping() {
if (m_rareData && m_rareData->compositedLayerMapping)
return;
ensureRareData().compositedLayerMapping =
wrapUnique(new CompositedLayerMapping(*this));
m_rareData->compositedLayerMapping->setNeedsGraphicsLayerUpdate(
GraphicsLayerUpdateSubtree);
updateOrRemoveFilterEffect();
}
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);
}
if (m_rareData)
m_rareData->compositedLayerMapping.reset();
if (!layerBeingDestroyed)
updateOrRemoveFilterEffect();
}
void PaintLayer::setGroupedMapping(CompositedLayerMapping* groupedMapping,
SetGroupMappingOptions options) {
CompositedLayerMapping* oldGroupedMapping = this->groupedMapping();
if (groupedMapping == oldGroupedMapping)
return;
if (options == InvalidateLayerAndRemoveFromMapping && oldGroupedMapping) {
oldGroupedMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree);
oldGroupedMapping->removeLayerFromSquashingGraphicsLayer(this);
}
if (m_rareData || groupedMapping)
ensureRareData().groupedMapping = groupedMapping;
#if DCHECK_IS_ON()
DCHECK(!groupedMapping || groupedMapping->verifyLayerInSquashingVector(this));
#endif
if (options == InvalidateLayerAndRemoveFromMapping && groupedMapping)
groupedMapping->setNeedsGraphicsLayerUpdate(GraphicsLayerUpdateSubtree);
}
bool PaintLayer::hasCompositedMask() const {
return m_rareData && m_rareData->compositedLayerMapping &&
m_rareData->compositedLayerMapping->hasMaskLayer();
}
bool PaintLayer::hasCompositedClippingMask() const {
return m_rareData && m_rareData->compositedLayerMapping &&
m_rareData->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 (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() != EVisibility::Visible)
return false;
if (paintsWithFilters() &&
layoutObject()->style()->filter().hasFilterThatAffectsOpacity())
return false;
// FIXME: Handle simple transforms.
if (transform() && compositingState() != PaintsIntoOwnBacking)
return false;
if (!RuntimeEnabledFeatures::compositeOpaqueFixedPositionEnabled() &&
layoutObject()->style()->position() == FixedPosition &&
compositingState() != PaintsIntoOwnBacking)
return false;
// This function should not be called when layer-lists are dirty.
// TODO(schenney) This check never hits in layout tests or most platforms, but
// does hit in PopupBlockerBrowserTest.AllowPopupThroughContentSetting on
// Win 7 Test Builder.
if (m_stackingNode->zOrderListsDirty())
return false;
// FIXME: We currently only check the immediate layoutObject,
// which will miss many cases where additional layout objects paint
// into this layer.
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()->hasClipRelatedProperty())
return false;
// TODO(schenney): This could be improved by unioning the opaque regions of
// all the children. That would require a refactoring because currently
// children just check they at least cover the given rect, but a unioning
// method would require children to compute and report their rects.
return childBackgroundIsKnownToBeOpaqueInRect(localRect);
}
bool PaintLayer::childBackgroundIsKnownToBeOpaqueInRect(
const LayoutRect& localRect) const {
PaintLayerStackingNodeReverseIterator reverseIterator(
*m_stackingNode,
PositiveZOrderChildren | NormalFlowChildren | NegativeZOrderChildren);
while (PaintLayerStackingNode* child = reverseIterator.next()) {
const PaintLayer* childLayer = child->layer();
// Stop at composited paint boundaries and non-self-painting layers.
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::isSelfPaintingLayerForIntrinsicOrScrollingReasons() const {
return layoutObject()->layerTypeRequired() == NormalPaintLayer ||
(m_scrollableArea && m_scrollableArea->hasOverlayScrollbars()) ||
needsCompositedScrolling();
}
bool PaintLayer::shouldBeSelfPaintingLayer() const {
if (layoutObject()->isLayoutPart() &&
toLayoutPart(layoutObject())->requiresAcceleratedCompositing())
return true;
return isSelfPaintingLayerForIntrinsicOrScrollingReasons();
}
bool PaintLayer::isSelfPaintingOnlyBecauseIsCompositedPart() const {
return shouldBeSelfPaintingLayer() &&
!isSelfPaintingLayerForIntrinsicOrScrollingReasons();
}
void PaintLayer::updateSelfPaintingLayer() {
bool isSelfPaintingLayer = shouldBeSelfPaintingLayer();
if (this->isSelfPaintingLayer() == isSelfPaintingLayer)
return;
m_isSelfPaintingLayer = isSelfPaintingLayer;
if (PaintLayer* parent = this->parent()) {
parent->dirtyAncestorChainHasSelfPaintingLayerDescendantStatus();
if (PaintLayer* enclosingSelfPaintingLayer =
parent->enclosingSelfPaintingLayer()) {
if (isSelfPaintingLayer)
mergeNeedsPaintPhaseFlagsFrom(*enclosingSelfPaintingLayer);
else
enclosingSelfPaintingLayer->mergeNeedsPaintPhaseFlagsFrom(*this);
}
}
}
PaintLayer* PaintLayer::enclosingSelfPaintingLayer() {
PaintLayer* layer = this;
while (layer && !layer->isSelfPaintingLayer())
layer = layer->parent();
return layer;
}
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.hasFilterInducingProperty() &&
(!oldStyle || !oldStyle->hasFilterInducingProperty()))
return;
updateOrRemoveFilterClients();
updateOrRemoveFilterEffect();
}
bool PaintLayer::attemptDirectCompositingUpdate(StyleDifference diff,
const ComputedStyle* oldStyle) {
CompositingReasons oldPotentialCompositingReasonsFromStyle =
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 (potentialCompositingReasonsFromStyle() !=
oldPotentialCompositingReasonsFromStyle)
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_rareData || !m_rareData->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_rareData ||
!(m_rareData->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_rareData->compositedLayerMapping->setNeedsGraphicsLayerUpdate(
GraphicsLayerUpdateLocal);
compositor()->setNeedsCompositingUpdate(CompositingUpdateAfterGeometryChange);
if (m_scrollableArea)
m_scrollableArea->updateAfterStyleChange(oldStyle);
return true;
}
void PaintLayer::styleDidChange(StyleDifference diff,
const ComputedStyle* oldStyle) {
if (attemptDirectCompositingUpdate(diff, oldStyle))
return;
m_stackingNode->styleDidChange(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();
updateDescendantDependentFlags();
updateTransform(oldStyle, layoutObject()->styleRef());
updateFilters(oldStyle, layoutObject()->styleRef());
setNeedsCompositingInputsUpdate();
}
bool PaintLayer::scrollsOverflow() const {
if (PaintLayerScrollableArea* scrollableArea = this->getScrollableArea())
return scrollableArea->scrollsOverflow();
return false;
}
FilterOperations PaintLayer::addReflectionToFilterOperations(
const ComputedStyle& style) const {
FilterOperations filterOperations = style.filter();
if (layoutObject()->hasReflection() && layoutObject()->isBox()) {
BoxReflection reflection = boxReflectionForPaintLayer(*this, style);
filterOperations.operations().append(
BoxReflectFilterOperation::create(reflection));
}
return filterOperations;
}
CompositorFilterOperations
PaintLayer::createCompositorFilterOperationsForFilter(
const ComputedStyle& style) {
FloatRect zoomedReferenceBox;
if (style.filter().hasReferenceFilter())
zoomedReferenceBox = boxForFilter();
FilterEffectBuilder builder(enclosingNode(), zoomedReferenceBox,
style.effectiveZoom());
return builder.buildFilterOperations(addReflectionToFilterOperations(style));
}
CompositorFilterOperations
PaintLayer::createCompositorFilterOperationsForBackdropFilter(
const ComputedStyle& style) {
FloatRect zoomedReferenceBox;
if (style.backdropFilter().hasReferenceFilter())
zoomedReferenceBox = boxForFilter();
FilterEffectBuilder builder(enclosingNode(), zoomedReferenceBox,
style.effectiveZoom());
return builder.buildFilterOperations(style.backdropFilter());
}
PaintLayerFilterInfo& PaintLayer::ensureFilterInfo() {
PaintLayerRareData& rareData = ensureRareData();
if (!rareData.filterInfo)
rareData.filterInfo = new PaintLayerFilterInfo(this);
return *rareData.filterInfo;
}
void PaintLayer::removeAncestorOverflowLayer(const PaintLayer* removedLayer) {
// If the current ancestor overflow layer does not match the removed layer
// the ancestor overflow layer has changed so we can stop searching.
if (ancestorOverflowLayer() && ancestorOverflowLayer() != removedLayer)
return;
if (ancestorOverflowLayer()) {
// TODO(pdr): When slimming paint v2 is enabled, we will need to
// invalidate the scroll paint property subtree for this so main
// thread scroll reasons are recomputed.
ancestorOverflowLayer()
->getScrollableArea()
->invalidateStickyConstraintsFor(this);
}
updateAncestorOverflowLayer(nullptr);
PaintLayer* current = m_first;
while (current) {
current->removeAncestorOverflowLayer(removedLayer);
current = current->nextSibling();
}
}
void PaintLayer::updateOrRemoveFilterClients() {
const auto& filter = layoutObject()->style()->filter();
if (filter.isEmpty() && m_rareData && m_rareData->filterInfo) {
m_rareData->filterInfo->clearLayer();
m_rareData->filterInfo = nullptr;
} else if (filter.hasReferenceFilter()) {
ensureFilterInfo().updateReferenceFilterClients(filter);
} else if (filterInfo()) {
filterInfo()->clearFilterReferences();
}
}
FilterEffect* PaintLayer::updateFilterEffect() const {
// TODO(chrishtr): ensure (and assert) that compositing is clean here.
if (!paintsWithFilters())
return nullptr;
PaintLayerFilterInfo* filterInfo = this->filterInfo();
// Should have been added by updateOrRemoveFilterEffect().
DCHECK(filterInfo);
if (filterInfo->lastEffect())
return filterInfo->lastEffect();
const ComputedStyle& style = layoutObject()->styleRef();
FloatRect zoomedReferenceBox;
if (style.filter().hasReferenceFilter())
zoomedReferenceBox = boxForFilter();
FilterEffectBuilder builder(enclosingNode(), zoomedReferenceBox,
style.effectiveZoom());
filterInfo->setLastEffect(
builder.buildFilterEffect(addReflectionToFilterOperations(style)));
return filterInfo->lastEffect();
}
FilterEffect* PaintLayer::lastFilterEffect() const {
return updateFilterEffect();
}
FloatRect PaintLayer::mapRectForFilter(const FloatRect& rect) const {
if (!hasFilterThatMovesPixels())
return rect;
// Ensure the filter-chain is refreshed wrt reference filters.
updateFilterEffect();
FilterOperations filterOperations =
addReflectionToFilterOperations(layoutObject()->styleRef());
return filterOperations.mapRect(rect);
}
LayoutRect PaintLayer::mapLayoutRectForFilter(const LayoutRect& rect) const {
if (!hasFilterThatMovesPixels())
return rect;
return enclosingLayoutRect(mapRectForFilter(FloatRect(rect)));
}
bool PaintLayer::hasFilterThatMovesPixels() const {
if (!hasFilterInducingProperty())
return false;
const ComputedStyle& style = layoutObject()->styleRef();
if (style.hasFilter() && style.filter().hasFilterThatMovesPixels())
return true;
if (style.hasBoxReflect())
return true;
return false;
}
void PaintLayer::updateOrRemoveFilterEffect() {
// FilterEffectBuilder is only used to render the filters in software mode,
// so we always need to run updateOrRemoveFilterEffect after the composited
// mode might have changed for this layer.
if (!paintsWithFilters()) {
if (PaintLayerFilterInfo* filterInfo = this->filterInfo())
filterInfo->setLastEffect(nullptr);
return;
}
ensureFilterInfo().setLastEffect(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 right
// before performing layout but after style recalc.
//
// See LayoutView::layout() and the call to
// invalidateSVGRootsWithRelativeLengthDescendents(). This violation is
// worked around in FrameView::updateStyleAndLayoutIfNeededRecursive() by
// doing an extra style recalc and layout in case it's needed.
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() {
setNeedsRepaintInternal();
// Do this unconditionally to ensure container chain is marked when
// compositing status of the layer changes.
markCompositingContainerChainForNeedsRepaint();
}
void PaintLayer::setNeedsRepaintInternal() {
m_needsRepaint = true;
setDisplayItemsUncached(); // Invalidate as a display item client.
}
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()) {
// TODO(wkorman): As we clean up the CompositedLayerMapping needsRepaint
// logic to delegate to scrollbars, we may be able to remove the line
// below as well.
groupedMapping->owningLayer().setNeedsRepaint();
return;
}
PaintLayer* container = layer->compositingContainer();
if (!container) {
LayoutItem owner = layer->layoutObject()->frame()->ownerLayoutItem();
if (owner.isNull())
break;
container = owner.enclosingLayer();
}
if (container->m_needsRepaint)
break;
container->setNeedsRepaintInternal();
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;
}
#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS
void PaintLayer::endShouldKeepAliveAllClientsRecursive() {
for (PaintLayer* child = firstChild(); child; child = child->nextSibling())
child->endShouldKeepAliveAllClientsRecursive();
DisplayItemClient::endShouldKeepAliveAllClients(this);
}
#endif
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,
layer);
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