/* * Copyright (C) 2006-2016 Apple Inc. All rights reserved. * * Portions are Copyright (C) 1998 Netscape Communications Corporation. * * Other contributors: * Robert O'Callahan * David Baron * Christian Biesinger * Randall Jesup * Roland Mainz * Josh Soref * Boris Zbarsky * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Alternatively, the contents of this file may be used under the terms * of either the Mozilla Public License Version 1.1, found at * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html * (the "GPL"), in which case the provisions of the MPL or the GPL are * applicable instead of those above. If you wish to allow use of your * version of this file only under the terms of one of those two * licenses (the MPL or the GPL) and not to allow others to use your * version of this file under the LGPL, indicate your decision by * deletingthe provisions above and replace them with the notice and * other provisions required by the MPL or the GPL, as the case may be. * If you do not delete the provisions above, a recipient may use your * version of this file under any of the LGPL, the MPL or the GPL. */ #include "config.h" #include "RenderLayer.h" #include "AnimationController.h" #include "BoxShape.h" #include "CSSPropertyNames.h" #include "Chrome.h" #include "DebugPageOverlays.h" #include "Document.h" #include "DocumentEventQueue.h" #include "DocumentMarkerController.h" #include "Element.h" #include "EventHandler.h" #include "FEColorMatrix.h" #include "FEMerge.h" #include "FilterEffectRenderer.h" #include "FloatConversion.h" #include "FloatPoint3D.h" #include "FloatRect.h" #include "FloatRoundedRect.h" #include "FlowThreadController.h" #include "FocusController.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameSelection.h" #include "FrameTree.h" #include "FrameView.h" #include "Gradient.h" #include "GraphicsContext.h" #include "HTMLFormControlElement.h" #include "HTMLFrameElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLNames.h" #include "HitTestingTransformState.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "Logging.h" #include "OverflowEvent.h" #include "OverlapTestRequestClient.h" #include "Page.h" #include "PlatformMouseEvent.h" #include "RenderFlowThread.h" #include "RenderGeometryMap.h" #include "RenderInline.h" #include "RenderIterator.h" #include "RenderLayerBacking.h" #include "RenderLayerCompositor.h" #include "RenderLayerFilterInfo.h" #include "RenderMarquee.h" #include "RenderMultiColumnFlowThread.h" #include "RenderNamedFlowFragment.h" #include "RenderNamedFlowThread.h" #include "RenderRegion.h" #include "RenderReplica.h" #include "RenderSVGResourceClipper.h" #include "RenderScrollbar.h" #include "RenderScrollbarPart.h" #include "RenderTableCell.h" #include "RenderTableRow.h" #include "RenderText.h" #include "RenderTheme.h" #include "RenderTreeAsText.h" #include "RenderView.h" #include "SVGNames.h" #include "ScaleTransformOperation.h" #include "ScrollAnimator.h" #include "Scrollbar.h" #include "ScrollbarTheme.h" #include "ScrollingCoordinator.h" #include "Settings.h" #include "ShadowRoot.h" #include "SourceGraphic.h" #include "StyleProperties.h" #include "StyleResolver.h" #include "TextStream.h" #include "TransformationMatrix.h" #include "TranslateTransformOperation.h" #include "WheelEventTestTrigger.h" #include #include #include #if ENABLE(CSS_SCROLL_SNAP) #include "AxisScrollSnapOffsets.h" #endif #define MIN_INTERSECT_FOR_REVEAL 32 namespace WebCore { using namespace HTMLNames; class ClipRects { WTF_MAKE_FAST_ALLOCATED; public: static Ref create() { return adoptRef(*new ClipRects); } static Ref create(const ClipRects& other) { return adoptRef(*new ClipRects(other)); } ClipRects() = default; void reset() { m_overflowClipRect.reset(); m_fixedClipRect.reset(); m_posClipRect.reset(); m_fixed = false; } const ClipRect& overflowClipRect() const { return m_overflowClipRect; } void setOverflowClipRect(const ClipRect& clipRect) { m_overflowClipRect = clipRect; } const ClipRect& fixedClipRect() const { return m_fixedClipRect; } void setFixedClipRect(const ClipRect& clipRect) { m_fixedClipRect = clipRect; } const ClipRect& posClipRect() const { return m_posClipRect; } void setPosClipRect(const ClipRect& clipRect) { m_posClipRect = clipRect; } bool fixed() const { return m_fixed; } void setFixed(bool fixed) { m_fixed = fixed; } void ref() { m_refCount++; } void deref() { if (!--m_refCount) delete this; } bool operator==(const ClipRects& other) const { return m_overflowClipRect == other.overflowClipRect() && m_fixedClipRect == other.fixedClipRect() && m_posClipRect == other.posClipRect() && m_fixed == other.fixed(); } ClipRects& operator=(const ClipRects& other) { m_overflowClipRect = other.overflowClipRect(); m_fixedClipRect = other.fixedClipRect(); m_posClipRect = other.posClipRect(); m_fixed = other.fixed(); return *this; } private: ClipRects(const LayoutRect& clipRect) : m_overflowClipRect(clipRect) , m_fixedClipRect(clipRect) , m_posClipRect(clipRect) { } ClipRects(const ClipRects& other) : m_overflowClipRect(other.overflowClipRect()) , m_fixedClipRect(other.fixedClipRect()) , m_posClipRect(other.posClipRect()) , m_fixed(other.fixed()) { } ClipRect m_overflowClipRect; ClipRect m_fixedClipRect; ClipRect m_posClipRect; unsigned m_refCount = 1; bool m_fixed = false; }; class ClipRectsCache { WTF_MAKE_FAST_ALLOCATED; public: ClipRectsCache() { #ifndef NDEBUG for (int i = 0; i < NumCachedClipRectsTypes; ++i) { m_clipRectsRoot[i] = 0; m_scrollbarRelevancy[i] = IgnoreOverlayScrollbarSize; } #endif } PassRefPtr getClipRects(ClipRectsType clipRectsType, ShouldRespectOverflowClip respectOverflow) { return m_clipRects[getIndex(clipRectsType, respectOverflow)]; } void setClipRects(ClipRectsType clipRectsType, ShouldRespectOverflowClip respectOverflow, PassRefPtr clipRects) { m_clipRects[getIndex(clipRectsType, respectOverflow)] = clipRects; } #ifndef NDEBUG const RenderLayer* m_clipRectsRoot[NumCachedClipRectsTypes]; OverlayScrollbarSizeRelevancy m_scrollbarRelevancy[NumCachedClipRectsTypes]; #endif private: int getIndex(ClipRectsType clipRectsType, ShouldRespectOverflowClip respectOverflow) { int index = static_cast(clipRectsType); if (respectOverflow == RespectOverflowClip) index += static_cast(NumCachedClipRectsTypes); return index; } RefPtr m_clipRects[NumCachedClipRectsTypes * 2]; }; void makeMatrixRenderable(TransformationMatrix& matrix, bool has3DRendering) { #if !ENABLE(3D_TRANSFORMS) UNUSED_PARAM(has3DRendering); matrix.makeAffine(); #else if (!has3DRendering) matrix.makeAffine(); #endif } RenderLayer::RenderLayer(RenderLayerModelObject& rendererLayerModelObject) : m_isRootLayer(rendererLayerModelObject.isRenderView()) , m_forcedStackingContext(rendererLayerModelObject.isMedia()) , m_inResizeMode(false) , m_scrollDimensionsDirty(true) , m_normalFlowListDirty(true) , m_hasSelfPaintingLayerDescendant(false) , m_hasSelfPaintingLayerDescendantDirty(false) , m_hasOutOfFlowPositionedDescendant(false) , m_hasOutOfFlowPositionedDescendantDirty(true) , m_needsCompositedScrolling(false) , m_descendantsAreContiguousInStackingOrder(false) , m_usedTransparency(false) , m_paintingInsideReflection(false) , m_inOverflowRelayout(false) , m_repaintStatus(NeedsNormalRepaint) , m_visibleContentStatusDirty(true) , m_hasVisibleContent(false) , m_visibleDescendantStatusDirty(false) , m_hasVisibleDescendant(false) , m_registeredScrollableArea(false) , m_3DTransformedDescendantStatusDirty(true) , m_has3DTransformedDescendant(false) , m_hasCompositingDescendant(false) , m_hasTransformedAncestor(false) , m_has3DTransformedAncestor(false) , m_indirectCompositingReason(static_cast(IndirectCompositingReason::None)) , m_viewportConstrainedNotCompositedReason(NoNotCompositedReason) #if PLATFORM(IOS) , m_adjustForIOSCaretWhenScrolling(false) #endif #if PLATFORM(IOS) #if ENABLE(IOS_TOUCH_EVENTS) , m_registeredAsTouchEventListenerForScrolling(false) #endif , m_inUserScroll(false) , m_requiresScrollBoundsOriginUpdate(false) #endif , m_containsDirtyOverlayScrollbars(false) , m_updatingMarqueePosition(false) #if !ASSERT_DISABLED , m_layerListMutationAllowed(true) #endif , m_hasFilterInfo(false) #if ENABLE(CSS_COMPOSITING) , m_blendMode(BlendModeNormal) , m_hasNotIsolatedCompositedBlendingDescendants(false) , m_hasNotIsolatedBlendingDescendants(false) , m_hasNotIsolatedBlendingDescendantsStatusDirty(false) #endif , m_renderer(rendererLayerModelObject) , m_parent(nullptr) , m_previous(nullptr) , m_next(nullptr) , m_first(nullptr) , m_last(nullptr) , m_staticInlinePosition(0) , m_staticBlockPosition(0) , m_enclosingPaginationLayer(nullptr) { m_isNormalFlowOnly = shouldBeNormalFlowOnly(); m_isSelfPaintingLayer = shouldBeSelfPaintingLayer(); // Non-stacking containers should have empty z-order lists. As this is already the case, // there is no need to dirty / recompute these lists. m_zOrderListsDirty = isStackingContainer(); if (!renderer().firstChild()) { m_visibleContentStatusDirty = false; m_hasVisibleContent = renderer().style().visibility() == VISIBLE; } if (Element* element = renderer().element()) { // We save and restore only the scrollOffset as the other scroll values are recalculated. m_scrollPosition = element->savedLayerScrollPosition(); if (!m_scrollPosition.isZero()) scrollAnimator().setCurrentPosition(m_scrollPosition); element->setSavedLayerScrollPosition(IntPoint()); } } RenderLayer::~RenderLayer() { if (inResizeMode() && !renderer().documentBeingDestroyed()) renderer().frame().eventHandler().resizeLayerDestroyed(); ASSERT(m_registeredScrollableArea == renderer().view().frameView().containsScrollableArea(this)); if (m_registeredScrollableArea) renderer().view().frameView().removeScrollableArea(this); if (!renderer().documentBeingDestroyed()) { #if ENABLE(IOS_TOUCH_EVENTS) unregisterAsTouchEventListenerForScrolling(); #endif if (Element* element = renderer().element()) element->setSavedLayerScrollPosition(m_scrollPosition); } destroyScrollbar(HorizontalScrollbar); destroyScrollbar(VerticalScrollbar); if (renderer().frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = renderer().frame().page()->scrollingCoordinator()) scrollingCoordinator->willDestroyScrollableArea(*this); } if (m_reflection) removeReflection(); FilterInfo::remove(*this); // Child layers will be deleted by their corresponding render objects, so // we don't need to delete them ourselves. clearBacking(true); } String RenderLayer::name() const { StringBuilder name; name.append(renderer().renderName()); if (Element* element = renderer().element()) { name.append(' '); name.append(element->tagName()); if (element->hasID()) { name.appendLiteral(" id=\'"); name.append(element->getIdAttribute()); name.append('\''); } if (element->hasClass()) { name.appendLiteral(" class=\'"); for (size_t i = 0; i < element->classNames().size(); ++i) { if (i > 0) name.append(' '); name.append(element->classNames()[i]); } name.append('\''); } } if (isReflection()) name.appendLiteral(" (reflection)"); return name.toString(); } RenderLayerCompositor& RenderLayer::compositor() const { return renderer().view().compositor(); } void RenderLayer::contentChanged(ContentChangeType changeType) { if ((changeType == CanvasChanged || changeType == VideoChanged || changeType == FullScreenChanged || changeType == ImageChanged) && compositor().updateLayerCompositingState(*this)) compositor().setCompositingLayersNeedRebuild(); if (m_backing) m_backing->contentChanged(changeType); } bool RenderLayer::canRender3DTransforms() const { return compositor().canRender3DTransforms(); } bool RenderLayer::paintsWithFilters() const { if (!renderer().hasFilter()) return false; if (!isComposited()) return true; if (!m_backing || !m_backing->canCompositeFilters()) return true; return false; } bool RenderLayer::requiresFullLayerImageForFilters() const { if (!paintsWithFilters()) return false; FilterEffectRenderer* renderer = filterRenderer(); return renderer && renderer->hasFilterThatMovesPixels(); } FilterEffectRenderer* RenderLayer::filterRenderer() const { FilterInfo* filterInfo = FilterInfo::getIfExists(*this); return filterInfo ? filterInfo->renderer() : nullptr; } void RenderLayer::updateLayerPositionsAfterLayout(const RenderLayer* rootLayer, UpdateLayerPositionsFlags flags) { RenderGeometryMap geometryMap(UseTransforms); if (this != rootLayer) geometryMap.pushMappingsToAncestor(parent(), nullptr); updateLayerPositions(&geometryMap, flags); } void RenderLayer::updateLayerPositions(RenderGeometryMap* geometryMap, UpdateLayerPositionsFlags flags) { updateLayerPosition(); // For relpositioned layers or non-positioned layers, // we need to keep in sync, since we may have shifted relative // to our parent layer. if (geometryMap) geometryMap->pushMappingsToAncestor(this, parent()); // Clear our cached clip rect information. clearClipRects(); if (hasOverflowControls()) { LayoutSize offsetFromRoot; if (geometryMap) offsetFromRoot = LayoutSize(toFloatSize(geometryMap->absolutePoint(FloatPoint()))); else { // FIXME: It looks suspicious to call convertToLayerCoords here // as canUseConvertToLayerCoords may be true for an ancestor layer. offsetFromRoot = offsetFromAncestor(root()); } positionOverflowControls(roundedIntSize(offsetFromRoot)); } updateDescendantDependentFlags(); if (flags & UpdatePagination) updatePagination(); else m_enclosingPaginationLayer = nullptr; if (m_hasVisibleContent) { // FIXME: LayoutState does not work with RenderLayers as there is not a 1-to-1 // mapping between them and the RenderObjects. It would be neat to enable // LayoutState outside the layout() phase and use it here. ASSERT(!renderer().view().layoutStateEnabled()); RenderLayerModelObject* repaintContainer = renderer().containerForRepaint(); LayoutRect oldRepaintRect = m_repaintRect; LayoutRect oldOutlineBox = m_outlineBox; computeRepaintRects(repaintContainer, geometryMap); // FIXME: Should ASSERT that value calculated for m_outlineBox using the cached offset is the same // as the value not using the cached offset, but we can't due to https://bugs.webkit.org/show_bug.cgi?id=37048 if (flags & CheckForRepaint) { if (!renderer().view().printing()) { bool didRepaint = false; if (m_repaintStatus & NeedsFullRepaint) { renderer().repaintUsingContainer(repaintContainer, oldRepaintRect); if (m_repaintRect != oldRepaintRect) { renderer().repaintUsingContainer(repaintContainer, m_repaintRect); didRepaint = true; } } else if (shouldRepaintAfterLayout()) { renderer().repaintAfterLayoutIfNeeded(repaintContainer, oldRepaintRect, oldOutlineBox, &m_repaintRect, &m_outlineBox); didRepaint = true; } if (didRepaint && renderer().isRenderNamedFlowFragmentContainer()) { // If we just repainted a region, we must also repaint the flow thread since it is the one // doing the actual painting of the flowed content. RenderNamedFlowFragment& region = *downcast(renderer()).renderNamedFlowFragment(); if (region.isValid()) region.flowThread()->layer()->repaintIncludingDescendants(); } } } } else clearRepaintRects(); m_repaintStatus = NeedsNormalRepaint; m_hasTransformedAncestor = flags & SeenTransformedLayer; m_has3DTransformedAncestor = flags & Seen3DTransformedLayer; // Update the reflection's position and size. if (m_reflection) m_reflection->layout(); // Clear the IsCompositingUpdateRoot flag once we've found the first compositing layer in this update. bool isUpdateRoot = (flags & IsCompositingUpdateRoot); if (isComposited()) flags &= ~IsCompositingUpdateRoot; if (renderer().isInFlowRenderFlowThread()) { updatePagination(); flags |= UpdatePagination; } if (transform()) { flags |= SeenTransformedLayer; if (!transform()->isAffine()) flags |= Seen3DTransformedLayer; } for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) child->updateLayerPositions(geometryMap, flags); if ((flags & UpdateCompositingLayers) && isComposited()) { RenderLayerBacking::UpdateAfterLayoutFlags updateFlags = RenderLayerBacking::CompositingChildrenOnly; if (flags & NeedsFullRepaintInBacking) updateFlags |= RenderLayerBacking::NeedsFullRepaint; if (isUpdateRoot) updateFlags |= RenderLayerBacking::IsUpdateRoot; backing()->updateAfterLayout(updateFlags); } // With all our children positioned, now update our marquee if we need to. if (m_marquee) { // FIXME: would like to use TemporaryChange<> but it doesn't work with bitfields. bool oldUpdatingMarqueePosition = m_updatingMarqueePosition; m_updatingMarqueePosition = true; m_marquee->updateMarqueePosition(); m_updatingMarqueePosition = oldUpdatingMarqueePosition; } if (geometryMap) geometryMap->popMappingsToAncestor(parent()); renderer().document().markers().invalidateRectsForAllMarkers(); } LayoutRect RenderLayer::repaintRectIncludingNonCompositingDescendants() const { LayoutRect repaintRect = m_repaintRect; for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { // Don't include repaint rects for composited child layers; they will paint themselves and have a different origin. if (child->isComposited()) continue; repaintRect.unite(child->repaintRectIncludingNonCompositingDescendants()); } return repaintRect; } void RenderLayer::setAncestorChainHasSelfPaintingLayerDescendant() { for (RenderLayer* layer = this; layer; layer = layer->parent()) { if (!layer->m_hasSelfPaintingLayerDescendantDirty && layer->hasSelfPaintingLayerDescendant()) break; layer->m_hasSelfPaintingLayerDescendantDirty = false; layer->m_hasSelfPaintingLayerDescendant = true; } } void RenderLayer::dirtyAncestorChainHasSelfPaintingLayerDescendantStatus() { for (RenderLayer* layer = this; layer; layer = layer->parent()) { layer->m_hasSelfPaintingLayerDescendantDirty = true; // If we have reached a self-painting layer, we know our parent should have a self-painting descendant // in this case, there is no need to dirty our ancestors further. if (layer->isSelfPaintingLayer()) { ASSERT(!parent() || parent()->m_hasSelfPaintingLayerDescendantDirty || parent()->hasSelfPaintingLayerDescendant()); break; } } } bool RenderLayer::acceleratedCompositingForOverflowScrollEnabled() const { return renderer().frame().settings().acceleratedCompositingForOverflowScrollEnabled(); } // If we are a stacking container, then this function will determine if our // descendants for a contiguous block in stacking order. This is required in // order for an element to be safely promoted to a stacking container. It is safe // to become a stacking container if this change would not alter the stacking // order of layers on the page. That can only happen if a non-descendant appear // between us and our descendants in stacking order. Here's an example: // // this // / | \. // A B C // /\ | /\. // 0 -8 D 2 7 // | // 5 // // I've labeled our normal flow descendants A, B, C, and D, our stacking // container descendants with their z indices, and us with 'this' (we're a // stacking container and our zIndex doesn't matter here). These nodes appear in // three lists: posZOrder, negZOrder, and normal flow (keep in mind that normal // flow layers don't overlap). So if we arrange these lists in order we get our // stacking order: // // [-8], [A-D], [0, 2, 5, 7]--> pos z-order. // | | // Neg z-order. <-+ +--> Normal flow descendants. // // We can then assign new, 'stacking' order indices to these elements as follows: // // [-8], [A-D], [0, 2, 5, 7] // 'Stacking' indices: -1 0 1 2 3 4 // // Note that the normal flow descendants can share an index because they don't // stack/overlap. Now our problem becomes very simple: a layer can safely become // a stacking container if the stacking-order indices of it and its descendants // appear in a contiguous block in the list of stacking indices. This problem // can be solved very efficiently by calculating the min/max stacking indices in // the subtree, and the number stacking container descendants. Once we have this // information, we know that the subtree's indices form a contiguous block if: // // maxStackIndex - minStackIndex == numSCDescendants // // So for node A in the example above we would have: // maxStackIndex = 1 // minStackIndex = -1 // numSCDecendants = 2 // // and so, // maxStackIndex - minStackIndex == numSCDescendants // ===> 1 - (-1) == 2 // ===> 2 == 2 // // Since this is true, A can safely become a stacking container. // Now, for node C we have: // // maxStackIndex = 4 // minStackIndex = 0 <-- because C has stacking index 0. // numSCDecendants = 2 // // and so, // maxStackIndex - minStackIndex == numSCDescendants // ===> 4 - 0 == 2 // ===> 4 == 2 // // Since this is false, C cannot be safely promoted to a stacking container. This // happened because of the elements with z-index 5 and 0. Now if 5 had been a // child of C rather than D, and A had no child with Z index 0, we would have had: // // maxStackIndex = 3 // minStackIndex = 0 <-- because C has stacking index 0. // numSCDecendants = 3 // // and so, // maxStackIndex - minStackIndex == numSCDescendants // ===> 3 - 0 == 3 // ===> 3 == 3 // // And we would conclude that C could be promoted. void RenderLayer::updateDescendantsAreContiguousInStackingOrder() { if (!isStackingContext() || !acceleratedCompositingForOverflowScrollEnabled()) return; ASSERT(!m_normalFlowListDirty); ASSERT(!m_zOrderListsDirty); std::unique_ptr> posZOrderList; std::unique_ptr> negZOrderList; rebuildZOrderLists(StopAtStackingContexts, posZOrderList, negZOrderList); // Create a reverse lookup. HashMap lookup; if (negZOrderList) { int stackingOrderIndex = -1; size_t listSize = negZOrderList->size(); for (size_t i = 0; i < listSize; ++i) { RenderLayer* currentLayer = negZOrderList->at(listSize - i - 1); if (!currentLayer->isStackingContext()) continue; lookup.set(currentLayer, stackingOrderIndex--); } } if (posZOrderList) { size_t listSize = posZOrderList->size(); int stackingOrderIndex = 1; for (size_t i = 0; i < listSize; ++i) { RenderLayer* currentLayer = posZOrderList->at(i); if (!currentLayer->isStackingContext()) continue; lookup.set(currentLayer, stackingOrderIndex++); } } int minIndex = 0; int maxIndex = 0; int count = 0; bool firstIteration = true; updateDescendantsAreContiguousInStackingOrderRecursive(lookup, minIndex, maxIndex, count, firstIteration); } void RenderLayer::updateDescendantsAreContiguousInStackingOrderRecursive(const HashMap& lookup, int& minIndex, int& maxIndex, int& count, bool firstIteration) { if (isStackingContext() && !firstIteration) { if (lookup.contains(this)) { minIndex = std::min(minIndex, lookup.get(this)); maxIndex = std::max(maxIndex, lookup.get(this)); count++; } return; } for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { int childMinIndex = 0; int childMaxIndex = 0; int childCount = 0; child->updateDescendantsAreContiguousInStackingOrderRecursive(lookup, childMinIndex, childMaxIndex, childCount, false); if (childCount) { count += childCount; minIndex = std::min(minIndex, childMinIndex); maxIndex = std::max(maxIndex, childMaxIndex); } } if (!isStackingContext()) { bool newValue = maxIndex - minIndex == count; bool didUpdate = newValue != m_descendantsAreContiguousInStackingOrder; m_descendantsAreContiguousInStackingOrder = newValue; if (didUpdate) updateNeedsCompositedScrolling(); } } void RenderLayer::computeRepaintRects(const RenderLayerModelObject* repaintContainer, const RenderGeometryMap* geometryMap) { ASSERT(!m_visibleContentStatusDirty); m_repaintRect = renderer().clippedOverflowRectForRepaint(repaintContainer); m_outlineBox = renderer().outlineBoundsForRepaint(repaintContainer, geometryMap); } void RenderLayer::computeRepaintRectsIncludingDescendants() { // FIXME: computeRepaintRects() has to walk up the parent chain for every layer to compute the rects. // We should make this more efficient. // FIXME: it's wrong to call this when layout is not up-to-date, which we do. computeRepaintRects(renderer().containerForRepaint()); for (RenderLayer* layer = firstChild(); layer; layer = layer->nextSibling()) layer->computeRepaintRectsIncludingDescendants(); } void RenderLayer::clearRepaintRects() { ASSERT(!m_hasVisibleContent); ASSERT(!m_visibleContentStatusDirty); m_repaintRect = LayoutRect(); m_outlineBox = LayoutRect(); } void RenderLayer::updateLayerPositionsAfterDocumentScroll() { ASSERT(this == renderer().view().layer()); RenderGeometryMap geometryMap(UseTransforms); updateLayerPositionsAfterScroll(&geometryMap); } void RenderLayer::updateLayerPositionsAfterOverflowScroll() { RenderGeometryMap geometryMap(UseTransforms); if (this != renderer().view().layer()) geometryMap.pushMappingsToAncestor(parent(), nullptr); // FIXME: why is it OK to not check the ancestors of this layer in order to // initialize the HasSeenViewportConstrainedAncestor and HasSeenAncestorWithOverflowClip flags? updateLayerPositionsAfterScroll(&geometryMap, IsOverflowScroll); } void RenderLayer::updateLayerPositionsAfterScroll(RenderGeometryMap* geometryMap, UpdateLayerPositionsAfterScrollFlags flags) { // FIXME: This shouldn't be needed, but there are some corner cases where // these flags are still dirty. Update so that the check below is valid. updateDescendantDependentFlags(); // If we have no visible content and no visible descendants, there is no point recomputing // our rectangles as they will be empty. If our visibility changes, we are expected to // recompute all our positions anyway. if (!m_hasVisibleDescendant && !m_hasVisibleContent) return; bool positionChanged = updateLayerPosition(); if (positionChanged) flags |= HasChangedAncestor; if (geometryMap) geometryMap->pushMappingsToAncestor(this, parent()); if (flags & HasChangedAncestor || flags & HasSeenViewportConstrainedAncestor || flags & IsOverflowScroll) clearClipRects(); if (renderer().style().hasViewportConstrainedPosition()) flags |= HasSeenViewportConstrainedAncestor; if (renderer().hasOverflowClip()) flags |= HasSeenAncestorWithOverflowClip; if (flags & HasSeenViewportConstrainedAncestor || (flags & IsOverflowScroll && flags & HasSeenAncestorWithOverflowClip)) { // FIXME: We could track the repaint container as we walk down the tree. computeRepaintRects(renderer().containerForRepaint(), geometryMap); } else { // Check that our cached rects are correct. ASSERT(m_repaintRect == renderer().clippedOverflowRectForRepaint(renderer().containerForRepaint())); ASSERT(m_outlineBox == renderer().outlineBoundsForRepaint(renderer().containerForRepaint(), geometryMap)); } for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) child->updateLayerPositionsAfterScroll(geometryMap, flags); // We don't update our reflection as scrolling is a translation which does not change the size() // of an object, thus RenderReplica will still repaint itself properly as the layer position was // updated above. if (m_marquee) { bool oldUpdatingMarqueePosition = m_updatingMarqueePosition; m_updatingMarqueePosition = true; m_marquee->updateMarqueePosition(); m_updatingMarqueePosition = oldUpdatingMarqueePosition; } if (geometryMap) geometryMap->popMappingsToAncestor(parent()); renderer().document().markers().invalidateRectsForAllMarkers(); } void RenderLayer::positionNewlyCreatedOverflowControls() { if (!backing()->hasUnpositionedOverflowControlsLayers()) return; RenderGeometryMap geometryMap(UseTransforms); if (this != renderer().view().layer() && parent()) geometryMap.pushMappingsToAncestor(parent(), nullptr); LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint())); positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot))); } #if ENABLE(CSS_COMPOSITING) void RenderLayer::updateBlendMode() { bool hadBlendMode = m_blendMode != BlendModeNormal; if (parent() && hadBlendMode != hasBlendMode()) { if (hasBlendMode()) parent()->updateAncestorChainHasBlendingDescendants(); else parent()->dirtyAncestorChainHasBlendingDescendants(); } BlendMode newBlendMode = renderer().style().blendMode(); if (newBlendMode != m_blendMode) m_blendMode = newBlendMode; } void RenderLayer::updateAncestorChainHasBlendingDescendants() { for (auto* layer = this; layer; layer = layer->parent()) { if (!layer->hasNotIsolatedBlendingDescendantsStatusDirty() && layer->hasNotIsolatedBlendingDescendants()) break; layer->m_hasNotIsolatedBlendingDescendants = true; layer->m_hasNotIsolatedBlendingDescendantsStatusDirty = false; layer->updateSelfPaintingLayer(); if (layer->isStackingContext()) break; } } void RenderLayer::dirtyAncestorChainHasBlendingDescendants() { for (auto* layer = this; layer; layer = layer->parent()) { if (layer->hasNotIsolatedBlendingDescendantsStatusDirty()) break; layer->m_hasNotIsolatedBlendingDescendantsStatusDirty = true; if (layer->isStackingContext()) break; } } #endif void RenderLayer::updateTransform() { bool hasTransform = renderer().hasTransform(); bool had3DTransform = has3DTransform(); bool hadTransform = !!m_transform; if (hasTransform != hadTransform) { if (hasTransform) m_transform = std::make_unique(); else m_transform = nullptr; // Layers with transforms act as clip rects roots, so clear the cached clip rects here. clearClipRectsIncludingDescendants(); } if (hasTransform) { RenderBox* box = renderBox(); ASSERT(box); m_transform->makeIdentity(); box->style().applyTransform(*m_transform, snapRectToDevicePixels(box->borderBoxRect(), box->document().deviceScaleFactor()), RenderStyle::IncludeTransformOrigin); makeMatrixRenderable(*m_transform, canRender3DTransforms()); } if (had3DTransform != has3DTransform()) dirty3DTransformedDescendantStatus(); } TransformationMatrix RenderLayer::currentTransform(RenderStyle::ApplyTransformOrigin applyOrigin) const { if (!m_transform) return TransformationMatrix(); RenderBox* box = renderBox(); ASSERT(box); if (renderer().style().isRunningAcceleratedAnimation()) { TransformationMatrix currTransform; FloatRect pixelSnappedBorderRect = snapRectToDevicePixels(box->borderBoxRect(), box->document().deviceScaleFactor()); RefPtr style = renderer().animation().getAnimatedStyleForRenderer(renderer()); style->applyTransform(currTransform, pixelSnappedBorderRect, applyOrigin); makeMatrixRenderable(currTransform, canRender3DTransforms()); return currTransform; } // m_transform includes transform-origin, so we need to recompute the transform here. if (applyOrigin == RenderStyle::ExcludeTransformOrigin) { TransformationMatrix currTransform; FloatRect pixelSnappedBorderRect = snapRectToDevicePixels(box->borderBoxRect(), box->document().deviceScaleFactor()); box->style().applyTransform(currTransform, pixelSnappedBorderRect, RenderStyle::ExcludeTransformOrigin); makeMatrixRenderable(currTransform, canRender3DTransforms()); return currTransform; } return *m_transform; } TransformationMatrix RenderLayer::renderableTransform(PaintBehavior paintBehavior) const { if (!m_transform) return TransformationMatrix(); if (paintBehavior & PaintBehaviorFlattenCompositingLayers) { TransformationMatrix matrix = *m_transform; makeMatrixRenderable(matrix, false /* flatten 3d */); return matrix; } return *m_transform; } RenderLayer* RenderLayer::enclosingOverflowClipLayer(IncludeSelfOrNot includeSelf) const { const RenderLayer* layer = (includeSelf == IncludeSelf) ? this : parent(); while (layer) { if (layer->renderer().hasOverflowClip()) return const_cast(layer); layer = layer->parent(); } return nullptr; } // FIXME: This is terrible. Bring back a cached bit for this someday. This crawl is going to slow down all // painting of content inside paginated layers. bool RenderLayer::hasCompositedLayerInEnclosingPaginationChain() const { // No enclosing layer means no compositing in the chain. if (!m_enclosingPaginationLayer) return false; // If the enclosing layer is composited, we don't have to check anything in between us and that // layer. if (m_enclosingPaginationLayer->isComposited()) return true; // If we are the enclosing pagination layer, then we can't be composited or we'd have passed the // previous check. if (m_enclosingPaginationLayer == this) return false; // The enclosing paginated layer is our ancestor and is not composited, so we have to check // intermediate layers between us and the enclosing pagination layer. Start with our own layer. if (isComposited()) return true; // For normal flow layers, we can recur up the layer tree. if (isNormalFlowOnly()) return parent()->hasCompositedLayerInEnclosingPaginationChain(); // Otherwise we have to go up the containing block chain. Find the first enclosing // containing block layer ancestor, and check that. for (const auto* containingBlock = renderer().containingBlock(); containingBlock && !is(*containingBlock); containingBlock = containingBlock->containingBlock()) { if (containingBlock->hasLayer()) return containingBlock->layer()->hasCompositedLayerInEnclosingPaginationChain(); } return false; } void RenderLayer::updatePagination() { m_enclosingPaginationLayer = nullptr; if (!parent()) return; // Each layer that is inside a multicolumn flow thread 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 (renderer().isInFlowRenderFlowThread()) { m_enclosingPaginationLayer = this; return; } if (isNormalFlowOnly()) { // Content inside a transform is not considered to be paginated, since we simply // paint the transform multiple times in each column, so we don't have to use // fragments for the transformed content. if (parent()->hasTransform()) m_enclosingPaginationLayer = nullptr; else m_enclosingPaginationLayer = parent()->enclosingPaginationLayer(IncludeCompositedPaginatedLayers); return; } // For the new columns code, we want to walk up our containing block chain looking for an enclosing layer. Once // we find one, then we just check its pagination status. for (const auto* containingBlock = renderer().containingBlock(); containingBlock && !is(*containingBlock); containingBlock = containingBlock->containingBlock()) { if (containingBlock->hasLayer()) { // Content inside a transform is not considered to be paginated, since we simply // paint the transform multiple times in each column, so we don't have to use // fragments for the transformed content. if (containingBlock->layer()->hasTransform()) m_enclosingPaginationLayer = nullptr; else m_enclosingPaginationLayer = containingBlock->layer()->enclosingPaginationLayer(IncludeCompositedPaginatedLayers); return; } } } bool RenderLayer::canBeStackingContainer() const { if (isStackingContext() || !stackingContainer()) return true; return m_descendantsAreContiguousInStackingOrder; } void RenderLayer::setHasVisibleContent() { if (m_hasVisibleContent && !m_visibleContentStatusDirty) { ASSERT(!parent() || parent()->hasVisibleDescendant()); return; } m_visibleContentStatusDirty = false; m_hasVisibleContent = true; computeRepaintRects(renderer().containerForRepaint()); if (!isNormalFlowOnly()) { // We don't collect invisible layers in z-order lists if we are not in compositing mode. // As we became visible, we need to dirty our stacking containers ancestors to be properly // collected. FIXME: When compositing, we could skip this dirtying phase. for (RenderLayer* sc = stackingContainer(); sc; sc = sc->stackingContainer()) { sc->dirtyZOrderLists(); if (sc->hasVisibleContent()) break; } } if (parent()) parent()->setAncestorChainHasVisibleDescendant(); } void RenderLayer::dirtyVisibleContentStatus() { m_visibleContentStatusDirty = true; if (parent()) parent()->dirtyAncestorChainVisibleDescendantStatus(); } void RenderLayer::dirtyAncestorChainVisibleDescendantStatus() { for (RenderLayer* layer = this; layer; layer = layer->parent()) { if (layer->m_visibleDescendantStatusDirty) break; layer->m_visibleDescendantStatusDirty = true; } } void RenderLayer::setAncestorChainHasVisibleDescendant() { for (RenderLayer* layer = this; layer; layer = layer->parent()) { if (!layer->m_visibleDescendantStatusDirty && layer->hasVisibleDescendant()) break; layer->m_hasVisibleDescendant = true; layer->m_visibleDescendantStatusDirty = false; } } void RenderLayer::updateDescendantDependentFlags(HashSet* outOfFlowDescendantContainingBlocks) { if (m_visibleDescendantStatusDirty || m_hasSelfPaintingLayerDescendantDirty || m_hasOutOfFlowPositionedDescendantDirty || hasNotIsolatedBlendingDescendantsStatusDirty()) { bool hasVisibleDescendant = false; bool hasSelfPaintingLayerDescendant = false; bool hasOutOfFlowPositionedDescendant = false; #if ENABLE(CSS_COMPOSITING) bool hasNotIsolatedBlendingDescendants = false; #endif HashSet childOutOfFlowDescendantContainingBlocks; for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { childOutOfFlowDescendantContainingBlocks.clear(); child->updateDescendantDependentFlags(&childOutOfFlowDescendantContainingBlocks); bool childIsOutOfFlowPositioned = child->renderer().isOutOfFlowPositioned(); if (childIsOutOfFlowPositioned) childOutOfFlowDescendantContainingBlocks.add(child->renderer().containingBlock()); if (outOfFlowDescendantContainingBlocks) { HashSet::const_iterator it = childOutOfFlowDescendantContainingBlocks.begin(); for (; it != childOutOfFlowDescendantContainingBlocks.end(); ++it) outOfFlowDescendantContainingBlocks->add(*it); } hasVisibleDescendant |= child->m_hasVisibleContent || child->m_hasVisibleDescendant; hasSelfPaintingLayerDescendant |= child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant(); hasOutOfFlowPositionedDescendant |= !childOutOfFlowDescendantContainingBlocks.isEmpty(); #if ENABLE(CSS_COMPOSITING) hasNotIsolatedBlendingDescendants |= child->hasBlendMode() || (child->hasNotIsolatedBlendingDescendants() && !child->isolatesBlending()); #endif bool allFlagsSet = hasVisibleDescendant && hasSelfPaintingLayerDescendant && hasOutOfFlowPositionedDescendant; #if ENABLE(CSS_COMPOSITING) allFlagsSet &= hasNotIsolatedBlendingDescendants; #endif if (allFlagsSet) break; } if (outOfFlowDescendantContainingBlocks) outOfFlowDescendantContainingBlocks->remove(&renderer()); m_hasVisibleDescendant = hasVisibleDescendant; m_visibleDescendantStatusDirty = false; m_hasSelfPaintingLayerDescendant = hasSelfPaintingLayerDescendant; m_hasSelfPaintingLayerDescendantDirty = false; m_hasOutOfFlowPositionedDescendant = hasOutOfFlowPositionedDescendant; if (m_hasOutOfFlowPositionedDescendantDirty) updateNeedsCompositedScrolling(); m_hasOutOfFlowPositionedDescendantDirty = false; #if ENABLE(CSS_COMPOSITING) m_hasNotIsolatedBlendingDescendants = hasNotIsolatedBlendingDescendants; if (m_hasNotIsolatedBlendingDescendantsStatusDirty) { m_hasNotIsolatedBlendingDescendantsStatusDirty = false; updateSelfPaintingLayer(); } #endif } if (m_visibleContentStatusDirty) { if (renderer().style().visibility() == VISIBLE) m_hasVisibleContent = true; else { // layer may be hidden but still have some visible content, check for this m_hasVisibleContent = false; RenderObject* r = renderer().firstChild(); while (r) { if (r->style().visibility() == VISIBLE && !r->hasLayer()) { m_hasVisibleContent = true; break; } RenderObject* child = nullptr; if (!r->hasLayer() && (child = r->firstChildSlow())) r = child; else if (r->nextSibling()) r = r->nextSibling(); else { do { r = r->parent(); if (r == &renderer()) r = nullptr; } while (r && !r->nextSibling()); if (r) r = r->nextSibling(); } } } m_visibleContentStatusDirty = false; } } void RenderLayer::dirty3DTransformedDescendantStatus() { RenderLayer* curr = stackingContainer(); if (curr) curr->m_3DTransformedDescendantStatusDirty = true; // This propagates up through preserve-3d hierarchies to the enclosing flattening layer. // Note that preserves3D() creates stacking context, so we can just run up the stacking containers. while (curr && curr->preserves3D()) { curr->m_3DTransformedDescendantStatusDirty = true; curr = curr->stackingContainer(); } } // Return true if this layer or any preserve-3d descendants have 3d. bool RenderLayer::update3DTransformedDescendantStatus() { if (m_3DTransformedDescendantStatusDirty) { m_has3DTransformedDescendant = false; 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. if (Vector* positiveZOrderList = posZOrderList()) { for (auto* layer : *positiveZOrderList) m_has3DTransformedDescendant |= layer->update3DTransformedDescendantStatus(); } // Now check our negative z-index children. if (Vector* negativeZOrderList = negZOrderList()) { for (auto* layer : *negativeZOrderList) m_has3DTransformedDescendant |= layer->update3DTransformedDescendantStatus(); } m_3DTransformedDescendantStatusDirty = false; } // If we live in a 3d hierarchy, then the layer at the root of that hierarchy needs // the m_has3DTransformedDescendant set. if (preserves3D()) return has3DTransform() || m_has3DTransformedDescendant; return has3DTransform(); } bool RenderLayer::updateLayerPosition() { LayoutPoint localPoint; LayoutSize inlineBoundingBoxOffset; // We don't put this into the RenderLayer x/y for inlines, so we need to subtract it out when done. if (renderer().isInline() && is(renderer())) { auto& inlineFlow = downcast(renderer()); IntRect lineBox = inlineFlow.linesBoundingBox(); setSize(lineBox.size()); inlineBoundingBoxOffset = toLayoutSize(lineBox.location()); localPoint += inlineBoundingBoxOffset; } else if (RenderBox* box = renderBox()) { // FIXME: Is snapping the size really needed here for the RenderBox case? setSize(snappedIntRect(box->frameRect()).size()); box->applyTopLeftLocationOffset(localPoint); } RenderElement* ancestor; if (!renderer().isOutOfFlowPositioned() && (ancestor = renderer().parent())) { // We must adjust our position by walking up the render tree looking for the // nearest enclosing object with a layer. while (ancestor && !ancestor->hasLayer()) { if (is(*ancestor) && !is(*ancestor)) { // Rows and cells share the same coordinate space (that of the section). // Omit them when computing our xpos/ypos. localPoint += downcast(*ancestor).topLeftLocationOffset(); } ancestor = ancestor->parent(); } if (is(*ancestor) && is(*ancestor)) { // Put ourselves into the row coordinate space. localPoint -= downcast(*ancestor).topLeftLocationOffset(); } } // Subtract our parent's scroll offset. RenderLayer* positionedParent; if (renderer().isOutOfFlowPositioned() && (positionedParent = enclosingAncestorForPosition(renderer().style().position()))) { // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. if (positionedParent->renderer().hasOverflowClip()) { LayoutSize offset = positionedParent->scrolledContentOffset(); localPoint -= offset; } if (renderer().isOutOfFlowPositioned() && positionedParent->renderer().isInFlowPositioned() && is(positionedParent->renderer())) { LayoutSize offset = downcast(positionedParent->renderer()).offsetForInFlowPositionedInline(&downcast(renderer())); localPoint += offset; } } else if (parent()) { if (parent()->renderer().hasOverflowClip()) { IntSize scrollOffset = parent()->scrolledContentOffset(); localPoint -= scrollOffset; } } bool positionOrOffsetChanged = false; if (renderer().isInFlowPositioned()) { LayoutSize newOffset = downcast(renderer()).offsetForInFlowPosition(); positionOrOffsetChanged = newOffset != m_offsetForInFlowPosition; m_offsetForInFlowPosition = newOffset; localPoint.move(m_offsetForInFlowPosition); } else { m_offsetForInFlowPosition = LayoutSize(); } // FIXME: We'd really like to just get rid of the concept of a layer rectangle and rely on the renderers. localPoint -= inlineBoundingBoxOffset; positionOrOffsetChanged |= location() != localPoint; setLocation(localPoint); return positionOrOffsetChanged; } TransformationMatrix RenderLayer::perspectiveTransform() const { RenderBox* box = renderBox(); if (!box) return TransformationMatrix(); if (!box->hasTransformRelatedProperty()) return TransformationMatrix(); const RenderStyle& style = box->style(); if (!style.hasPerspective()) return TransformationMatrix(); // Maybe fetch the perspective from the backing? const FloatRect borderBox = snapRectToDevicePixels(box->borderBoxRect(), box->document().deviceScaleFactor()); float perspectiveOriginX = floatValueForLength(style.perspectiveOriginX(), borderBox.width()); float perspectiveOriginY = floatValueForLength(style.perspectiveOriginY(), borderBox.height()); // A perspective origin of 0,0 makes the vanishing point in the center of the element. // We want it to be in the top-left, so subtract half the height and width. perspectiveOriginX -= borderBox.width() / 2.0f; perspectiveOriginY -= borderBox.height() / 2.0f; TransformationMatrix t; t.translate(perspectiveOriginX, perspectiveOriginY); t.applyPerspective(style.perspective()); t.translate(-perspectiveOriginX, -perspectiveOriginY); return t; } FloatPoint RenderLayer::perspectiveOrigin() const { if (!renderer().hasTransformRelatedProperty()) return FloatPoint(); const LayoutRect borderBox = downcast(renderer()).borderBoxRect(); const RenderStyle& style = renderer().style(); return FloatPoint(floatValueForLength(style.perspectiveOriginX(), borderBox.width()), floatValueForLength(style.perspectiveOriginY(), borderBox.height())); } RenderLayer* RenderLayer::stackingContainer() const { RenderLayer* layer = parent(); while (layer && !layer->isStackingContainer()) layer = layer->parent(); ASSERT(!layer || layer->isStackingContainer()); return layer; } static inline bool isContainerForPositioned(RenderLayer& layer, EPosition position) { switch (position) { case FixedPosition: return layer.renderer().canContainFixedPositionObjects(); case AbsolutePosition: return layer.renderer().canContainAbsolutelyPositionedObjects(); default: ASSERT_NOT_REACHED(); return false; } } RenderLayer* RenderLayer::enclosingAncestorForPosition(EPosition position) const { RenderLayer* curr = parent(); while (curr && !isContainerForPositioned(*curr, position)) curr = curr->parent(); return curr; } static RenderLayer* parentLayerCrossFrame(const RenderLayer& layer) { if (layer.parent()) return layer.parent(); HTMLFrameOwnerElement* ownerElement = layer.renderer().document().ownerElement(); if (!ownerElement) return nullptr; RenderElement* ownerRenderer = ownerElement->renderer(); if (!ownerRenderer) return nullptr; return ownerRenderer->enclosingLayer(); } RenderLayer* RenderLayer::enclosingScrollableLayer() const { for (RenderLayer* nextLayer = parentLayerCrossFrame(*this); nextLayer; nextLayer = parentLayerCrossFrame(*nextLayer)) { if (is(nextLayer->renderer()) && downcast(nextLayer->renderer()).canBeScrolledAndHasScrollableArea()) return nextLayer; } return nullptr; } IntRect RenderLayer::scrollableAreaBoundingBox(bool* isInsideFixed) const { return renderer().absoluteBoundingBoxRect(/* useTransforms */ true, isInsideFixed); } bool RenderLayer::isRubberBandInProgress() const { #if ENABLE(RUBBER_BANDING) if (!scrollsOverflow()) return false; if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) return scrollAnimator->isRubberBandInProgress(); #endif return false; } bool RenderLayer::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const { Page* page = renderer().frame().page(); return page && page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting(); } RenderLayer* RenderLayer::enclosingTransformedAncestor() const { RenderLayer* curr = parent(); while (curr && !curr->isRootLayer() && !curr->transform()) curr = curr->parent(); return curr; } static inline const RenderLayer* compositingContainer(const RenderLayer& layer) { return layer.isNormalFlowOnly() ? layer.parent() : layer.stackingContainer(); } inline bool RenderLayer::shouldRepaintAfterLayout() const { if (m_repaintStatus == NeedsNormalRepaint) return true; // Composited layers that were moved during a positioned movement only // layout, don't need to be repainted. They just need to be recomposited. ASSERT(m_repaintStatus == NeedsFullRepaintForPositionedMovementLayout); return !isComposited() || backing()->paintsIntoCompositedAncestor(); } bool compositedWithOwnBackingStore(const RenderLayer& layer) { return layer.isComposited() && !layer.backing()->paintsIntoCompositedAncestor(); } RenderLayer* RenderLayer::enclosingCompositingLayer(IncludeSelfOrNot includeSelf) const { if (includeSelf == IncludeSelf && isComposited()) return const_cast(this); for (const RenderLayer* curr = compositingContainer(*this); curr; curr = compositingContainer(*curr)) { if (curr->isComposited()) return const_cast(curr); } return nullptr; } RenderLayer* RenderLayer::enclosingCompositingLayerForRepaint(IncludeSelfOrNot includeSelf) const { if (includeSelf == IncludeSelf && compositedWithOwnBackingStore(*this)) return const_cast(this); for (const RenderLayer* curr = compositingContainer(*this); curr; curr = compositingContainer(*curr)) { if (compositedWithOwnBackingStore(*curr)) return const_cast(curr); } return nullptr; } RenderLayer* RenderLayer::enclosingFilterLayer(IncludeSelfOrNot includeSelf) const { const RenderLayer* curr = (includeSelf == IncludeSelf) ? this : parent(); for (; curr; curr = curr->parent()) { if (curr->requiresFullLayerImageForFilters()) return const_cast(curr); } return nullptr; } RenderLayer* RenderLayer::enclosingFilterRepaintLayer() const { for (const RenderLayer* curr = this; curr; curr = curr->parent()) { if ((curr != this && curr->requiresFullLayerImageForFilters()) || compositedWithOwnBackingStore(*curr) || curr->isRootLayer()) return const_cast(curr); } return nullptr; } void RenderLayer::setFilterBackendNeedsRepaintingInRect(const LayoutRect& rect) { if (rect.isEmpty()) return; LayoutRect rectForRepaint = rect; renderer().style().filterOutsets().expandRect(rectForRepaint); FilterInfo& filterInfo = FilterInfo::get(*this); filterInfo.expandDirtySourceRect(rectForRepaint); RenderLayer* parentLayer = enclosingFilterRepaintLayer(); ASSERT(parentLayer); FloatQuad repaintQuad(rectForRepaint); LayoutRect parentLayerRect = renderer().localToContainerQuad(repaintQuad, &parentLayer->renderer()).enclosingBoundingBox(); if (parentLayer->isComposited()) { if (!parentLayer->backing()->paintsIntoWindow()) { parentLayer->setBackingNeedsRepaintInRect(parentLayerRect); return; } // If the painting goes to window, redirect the painting to the parent RenderView. parentLayer = renderer().view().layer(); parentLayerRect = renderer().localToContainerQuad(repaintQuad, &parentLayer->renderer()).enclosingBoundingBox(); } if (parentLayer->paintsWithFilters()) { parentLayer->setFilterBackendNeedsRepaintingInRect(parentLayerRect); return; } if (parentLayer->isRootLayer()) { downcast(parentLayer->renderer()).repaintViewRectangle(parentLayerRect); return; } ASSERT_NOT_REACHED(); } bool RenderLayer::hasAncestorWithFilterOutsets() const { for (const RenderLayer* curr = this; curr; curr = curr->parent()) { if (curr->renderer().style().hasFilterOutsets()) return true; } return false; } RenderLayer* RenderLayer::clippingRootForPainting() const { if (isComposited()) return const_cast(this); const RenderLayer* current = this; while (current) { if (current->isRootLayer()) return const_cast(current); current = compositingContainer(*current); ASSERT(current); if (current->transform() || compositedWithOwnBackingStore(*current)) return const_cast(current); } ASSERT_NOT_REACHED(); return nullptr; } LayoutPoint RenderLayer::absoluteToContents(const LayoutPoint& absolutePoint) const { // We don't use convertToLayerCoords because it doesn't know about transforms return LayoutPoint(renderer().absoluteToLocal(absolutePoint, UseTransforms)); } bool RenderLayer::cannotBlitToWindow() const { if (isTransparent() || hasReflection() || hasTransform()) return true; if (!parent()) return false; return parent()->cannotBlitToWindow(); } RenderLayer* RenderLayer::transparentPaintingAncestor() { if (isComposited()) return nullptr; for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { if (curr->isComposited()) return nullptr; if (curr->isTransparent()) return curr; } return nullptr; } enum TransparencyClipBoxBehavior { PaintingTransparencyClipBox, HitTestingTransparencyClipBox }; enum TransparencyClipBoxMode { DescendantsOfTransparencyClipBox, RootOfTransparencyClipBox }; static LayoutRect transparencyClipBox(const RenderLayer&, const RenderLayer* rootLayer, TransparencyClipBoxBehavior, TransparencyClipBoxMode, PaintBehavior = 0); static void expandClipRectForRegionAndReflection(LayoutRect& clipRect, const RenderLayer& layer, const RenderLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior, PaintBehavior paintBehavior) { // If this is a region, then the painting is actually done by its flow thread's layer. if (layer.renderer().isRenderNamedFlowFragmentContainer()) { RenderBlockFlow& regionContainer = downcast(layer.renderer()); RenderNamedFlowFragment& region = *regionContainer.renderNamedFlowFragment(); RenderLayer* flowThreadLayer = region.flowThread()->layer(); if (flowThreadLayer && (!layer.reflection() || layer.reflectionLayer() != flowThreadLayer)) { LayoutRect flowThreadClipRect = transparencyClipBox(*flowThreadLayer, rootLayer, transparencyBehavior, DescendantsOfTransparencyClipBox, paintBehavior); LayoutSize moveOffset = (regionContainer.contentBoxRect().location() + layer.offsetFromAncestor(flowThreadLayer)) - region.flowThreadPortionRect().location(); flowThreadClipRect.move(moveOffset); clipRect.unite(flowThreadClipRect); } } } static void expandClipRectForDescendantsAndReflection(LayoutRect& clipRect, const RenderLayer& layer, const RenderLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior, PaintBehavior paintBehavior) { // If we have a mask, then the clip is limited to the border box area (and there is // no need to examine child layers). if (!layer.renderer().hasMask()) { // Note: we don't have to walk z-order lists since transparent elements always establish // a stacking container. This means we can just walk the layer tree directly. for (RenderLayer* curr = layer.firstChild(); curr; curr = curr->nextSibling()) { if (!layer.reflection() || layer.reflectionLayer() != curr) clipRect.unite(transparencyClipBox(*curr, rootLayer, transparencyBehavior, DescendantsOfTransparencyClipBox, paintBehavior)); } } expandClipRectForRegionAndReflection(clipRect, layer, rootLayer, transparencyBehavior, paintBehavior); // If we have a reflection, then we need to account for that when we push the clip. Reflect our entire // current transparencyClipBox to catch all child layers. // FIXME: Accelerated compositing will eventually want to do something smart here to avoid incorporating this // size into the parent layer. if (layer.renderer().hasReflection()) { LayoutSize delta = layer.offsetFromAncestor(rootLayer); clipRect.move(-delta); clipRect.unite(layer.renderBox()->reflectedRect(clipRect)); clipRect.move(delta); } } static LayoutRect transparencyClipBox(const RenderLayer& layer, const RenderLayer* rootLayer, TransparencyClipBoxBehavior transparencyBehavior, TransparencyClipBoxMode transparencyMode, PaintBehavior paintBehavior) { // 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(paintBehavior)) || (transparencyBehavior == HitTestingTransparencyClipBox && layer.hasTransform()))) { // The best we can do here is to use enclosed bounding boxes to establish a "fuzzy" enough clip to encompass // the transformed layer and all of its children. RenderLayer::PaginationInclusionMode mode = transparencyBehavior == HitTestingTransparencyClipBox ? RenderLayer::IncludeCompositedPaginatedLayers : RenderLayer::ExcludeCompositedPaginatedLayers; const RenderLayer* paginationLayer = transparencyMode == DescendantsOfTransparencyClipBox ? layer.enclosingPaginationLayer(mode) : nullptr; const RenderLayer* rootLayerForTransform = paginationLayer ? paginationLayer : rootLayer; LayoutSize delta = layer.offsetFromAncestor(rootLayerForTransform); TransformationMatrix transform; transform.translate(delta.width(), delta.height()); transform.multiply(*layer.transform()); // We don't use fragment boxes when collecting a transformed layer's bounding box, since it always // paints unfragmented. LayoutRect clipRect = layer.boundingBox(&layer); expandClipRectForDescendantsAndReflection(clipRect, layer, &layer, transparencyBehavior, paintBehavior); layer.renderer().style().filterOutsets().expandRect(clipRect); LayoutRect result = transform.mapRect(clipRect); if (!paginationLayer) return result; // We have to break up the transformed extent across our columns. // Split our box up into the actual fragment boxes that render in the columns/pages and unite those together to // get our true bounding box. auto& enclosingFlowThread = downcast(paginationLayer->renderer()); result = enclosingFlowThread.fragmentsBoundingBox(result); result.move(paginationLayer->offsetFromAncestor(rootLayer)); return result; } LayoutRect clipRect = layer.boundingBox(rootLayer, layer.offsetFromAncestor(rootLayer), transparencyBehavior == HitTestingTransparencyClipBox ? RenderLayer::UseFragmentBoxesIncludingCompositing : RenderLayer::UseFragmentBoxesExcludingCompositing); expandClipRectForDescendantsAndReflection(clipRect, layer, rootLayer, transparencyBehavior, paintBehavior); layer.renderer().style().filterOutsets().expandRect(clipRect); return clipRect; } static LayoutRect paintingExtent(const RenderLayer& currentLayer, const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, PaintBehavior paintBehavior) { return intersection(transparencyClipBox(currentLayer, rootLayer, PaintingTransparencyClipBox, RootOfTransparencyClipBox, paintBehavior), paintDirtyRect); } void RenderLayer::beginTransparencyLayers(GraphicsContext& context, const LayerPaintingInfo& paintingInfo, const LayoutRect& dirtyRect) { if (context.paintingDisabled() || (paintsWithTransparency(paintingInfo.paintBehavior) && m_usedTransparency)) return; RenderLayer* ancestor = transparentPaintingAncestor(); if (ancestor) ancestor->beginTransparencyLayers(context, paintingInfo, dirtyRect); if (paintsWithTransparency(paintingInfo.paintBehavior)) { m_usedTransparency = true; context.save(); LayoutRect adjustedClipRect = paintingExtent(*this, paintingInfo.rootLayer, dirtyRect, paintingInfo.paintBehavior); adjustedClipRect.move(paintingInfo.subpixelAccumulation); FloatRect pixelSnappedClipRect = snapRectToDevicePixels(adjustedClipRect, renderer().document().deviceScaleFactor()); context.clip(pixelSnappedClipRect); #if ENABLE(CSS_COMPOSITING) bool usesCompositeOperation = hasBlendMode() && !(renderer().isSVGRoot() && parent() && parent()->isRootLayer()); if (usesCompositeOperation) context.setCompositeOperation(context.compositeOperation(), blendMode()); #endif context.beginTransparencyLayer(renderer().opacity()); #if ENABLE(CSS_COMPOSITING) if (usesCompositeOperation) context.setCompositeOperation(context.compositeOperation(), BlendModeNormal); #endif #ifdef REVEAL_TRANSPARENCY_LAYERS context->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f)); context->fillRect(pixelSnappedClipRect); #endif } } #if PLATFORM(IOS) void RenderLayer::willBeDestroyed() { if (RenderLayerBacking* layerBacking = backing()) layerBacking->layerWillBeDestroyed(); } #endif void RenderLayer::addChild(RenderLayer* child, RenderLayer* beforeChild) { RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild(); if (prevSibling) { child->setPreviousSibling(prevSibling); prevSibling->setNextSibling(child); ASSERT(prevSibling != child); } else setFirstChild(child); if (beforeChild) { beforeChild->setPreviousSibling(child); child->setNextSibling(beforeChild); ASSERT(beforeChild != child); } else setLastChild(child); child->setParent(this); if (child->isNormalFlowOnly()) dirtyNormalFlowList(); if (!child->isNormalFlowOnly() || child->firstChild()) { // Dirty the z-order list in which we are contained. The stackingContainer() 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->dirtyStackingContainerZOrderLists(); } child->updateDescendantDependentFlags(); if (child->m_hasVisibleContent || child->m_hasVisibleDescendant) setAncestorChainHasVisibleDescendant(); if (child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant()) setAncestorChainHasSelfPaintingLayerDescendant(); if (child->renderer().isOutOfFlowPositioned() || child->hasOutOfFlowPositionedDescendant()) setAncestorChainHasOutOfFlowPositionedDescendant(child->renderer().containingBlock()); #if ENABLE(CSS_COMPOSITING) if (child->hasBlendMode() || (child->hasNotIsolatedBlendingDescendants() && !child->isolatesBlending())) updateAncestorChainHasBlendingDescendants(); #endif compositor().layerWasAdded(*this, *child); } RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild) { if (!renderer().documentBeingDestroyed()) compositor().layerWillBeRemoved(*this, *oldChild); // remove the child 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->isNormalFlowOnly()) dirtyNormalFlowList(); if (!oldChild->isNormalFlowOnly() || oldChild->firstChild()) { // Dirty the z-order list in which we are contained. When called via the // reattachment process in removeOnlyThisLayer, the layer may already be disconnected // from the main layer tree, so we need to null-check the |stackingContainer| value. oldChild->dirtyStackingContainerZOrderLists(); } if (oldChild->renderer().isOutOfFlowPositioned() || oldChild->hasOutOfFlowPositionedDescendant()) dirtyAncestorChainHasOutOfFlowPositionedDescendantStatus(); oldChild->setPreviousSibling(nullptr); oldChild->setNextSibling(nullptr); oldChild->setParent(nullptr); oldChild->updateDescendantDependentFlags(); if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant) dirtyAncestorChainVisibleDescendantStatus(); if (oldChild->isSelfPaintingLayer() || oldChild->hasSelfPaintingLayerDescendant()) dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); #if ENABLE(CSS_COMPOSITING) if (oldChild->hasBlendMode() || (oldChild->hasNotIsolatedBlendingDescendants() && !oldChild->isolatesBlending())) dirtyAncestorChainHasBlendingDescendants(); #endif return oldChild; } void RenderLayer::removeOnlyThisLayer() { if (!m_parent) return; // Mark that we are about to lose our layer. This makes render tree // walks ignore this layer while we're removing it. renderer().setHasLayer(false); compositor().layerWillBeRemoved(*m_parent, *this); // Dirty the clip rects. clearClipRectsIncludingDescendants(); RenderLayer* nextSib = nextSibling(); // Remove the child reflection layer before moving other child layers. // The reflection layer should not be moved to the parent. if (reflection()) removeChild(reflectionLayer()); // Now walk our kids and reattach them to our parent. RenderLayer* current = m_first; while (current) { RenderLayer* next = current->nextSibling(); removeChild(current); m_parent->addChild(current, nextSib); current->setRepaintStatus(NeedsFullRepaint); // updateLayerPositions depends on hasLayer() already being false for proper layout. ASSERT(!renderer().hasLayer()); current->updateLayerPositions(); // FIXME: use geometry map. current = next; } // Remove us from the parent. m_parent->removeChild(this); renderer().destroyLayer(); } void RenderLayer::insertOnlyThisLayer() { if (!m_parent && renderer().parent()) { // We need to connect ourselves when our renderer() has a parent. // Find our enclosingLayer and add ourselves. RenderLayer* parentLayer = renderer().parent()->enclosingLayer(); ASSERT(parentLayer); RenderLayer* beforeChild = parentLayer->reflectionLayer() != this ? renderer().parent()->findNextLayer(parentLayer, &renderer()) : nullptr; parentLayer->addChild(this, beforeChild); } // Remove all descendant layers from the hierarchy and add them to the new position. for (auto& child : childrenOfType(renderer())) child.moveLayers(m_parent, this); // Clear out all the clip rects. clearClipRectsIncludingDescendants(); } void RenderLayer::convertToPixelSnappedLayerCoords(const RenderLayer* ancestorLayer, IntPoint& roundedLocation, ColumnOffsetAdjustment adjustForColumns) const { LayoutPoint location = convertToLayerCoords(ancestorLayer, roundedLocation, adjustForColumns); roundedLocation = roundedIntPoint(location); } // Returns the layer reached on the walk up towards the ancestor. static inline const RenderLayer* accumulateOffsetTowardsAncestor(const RenderLayer* layer, const RenderLayer* ancestorLayer, LayoutPoint& location, RenderLayer::ColumnOffsetAdjustment adjustForColumns) { ASSERT(ancestorLayer != layer); const RenderLayerModelObject& renderer = layer->renderer(); EPosition position = renderer.style().position(); // FIXME: Special casing RenderFlowThread so much for fixed positioning here is not great. RenderFlowThread* fixedFlowThreadContainer = position == FixedPosition ? renderer.flowThreadContainingBlock() : nullptr; if (fixedFlowThreadContainer && !fixedFlowThreadContainer->isOutOfFlowPositioned()) fixedFlowThreadContainer = nullptr; // FIXME: Positioning of out-of-flow(fixed, absolute) elements collected in a RenderFlowThread // may need to be revisited in a future patch. // If the fixed renderer is inside a RenderFlowThread, we should not compute location using localToAbsolute, // since localToAbsolute maps the coordinates from named flow to regions coordinates and regions can be // positioned in a completely different place in the viewport (RenderView). if (position == FixedPosition && !fixedFlowThreadContainer && (!ancestorLayer || ancestorLayer == renderer.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 RenderView. FloatPoint absPos = renderer.localToAbsolute(FloatPoint(), IsFixed); location += LayoutSize(absPos.x(), absPos.y()); return ancestorLayer; } // For the fixed positioned elements inside a render flow thread, we should also skip the code path below // Otherwise, for the case of ancestorLayer == rootLayer and fixed positioned element child of a transformed // element in render flow thread, we will hit the fixed positioned container before hitting the ancestor layer. if (position == FixedPosition && !fixedFlowThreadContainer) { // For a fixed layers, we need to walk up to the root to see if there's a fixed position container // (e.g. a transformed layer). It's an error to call offsetFromAncestor() across a layer with a transform, // so we should always find the ancestor at or before we find the fixed position container. RenderLayer* fixedPositionContainerLayer = nullptr; bool foundAncestor = false; for (RenderLayer* currLayer = layer->parent(); currLayer; currLayer = currLayer->parent()) { if (currLayer == ancestorLayer) foundAncestor = true; if (isContainerForPositioned(*currLayer, FixedPosition)) { fixedPositionContainerLayer = currLayer; ASSERT_UNUSED(foundAncestor, foundAncestor); break; } } ASSERT(fixedPositionContainerLayer); // We should have hit the RenderView's layer at least. if (fixedPositionContainerLayer != ancestorLayer) { LayoutSize fixedContainerCoords = layer->offsetFromAncestor(fixedPositionContainerLayer); LayoutSize ancestorCoords = ancestorLayer->offsetFromAncestor(fixedPositionContainerLayer); location += (fixedContainerCoords - ancestorCoords); return ancestorLayer; } } if (position == FixedPosition && fixedFlowThreadContainer) { ASSERT(ancestorLayer); if (ancestorLayer->isOutOfFlowRenderFlowThread()) { location += toLayoutSize(layer->location()); return ancestorLayer; } if (ancestorLayer == renderer.view().layer()) { // Add location in flow thread coordinates. location += toLayoutSize(layer->location()); // Add flow thread offset in view coordinates since the view may be scrolled. FloatPoint absPos = renderer.view().localToAbsolute(FloatPoint(), IsFixed); location += LayoutSize(absPos.x(), absPos.y()); return ancestorLayer; } } RenderLayer* parentLayer; if (position == AbsolutePosition || position == FixedPosition) { // Do what enclosingAncestorForPosition() does, but check for ancestorLayer along the way. parentLayer = layer->parent(); bool foundAncestorFirst = false; while (parentLayer) { // RenderFlowThread is a positioned container, child of RenderView, positioned at (0,0). // This implies that, for out-of-flow positioned elements inside a RenderFlowThread, // we are bailing out before reaching root layer. if (isContainerForPositioned(*parentLayer, position)) break; if (parentLayer == ancestorLayer) { foundAncestorFirst = true; break; } parentLayer = parentLayer->parent(); } // We should not reach RenderView layer past the RenderFlowThread layer for any // children of the RenderFlowThread. if (renderer.flowThreadContainingBlock() && !layer->isOutOfFlowRenderFlowThread()) ASSERT(parentLayer != renderer.view().layer()); if (foundAncestorFirst) { // Found ancestorLayer before the abs. positioned container, so compute offset of both relative // to enclosingAncestorForPosition and subtract. RenderLayer* positionedAncestor = parentLayer->enclosingAncestorForPosition(position); LayoutSize thisCoords = layer->offsetFromAncestor(positionedAncestor); LayoutSize ancestorCoords = ancestorLayer->offsetFromAncestor(positionedAncestor); location += (thisCoords - ancestorCoords); return ancestorLayer; } } else parentLayer = layer->parent(); if (!parentLayer) return nullptr; location += toLayoutSize(layer->location()); if (adjustForColumns == RenderLayer::AdjustForColumns) { if (RenderLayer* parentLayer = layer->parent()) { if (is(parentLayer->renderer())) { RenderRegion* region = downcast(parentLayer->renderer()).physicalTranslationFromFlowToRegion(location); if (region) location.moveBy(region->topLeftLocation() + -parentLayer->renderBox()->topLeftLocation()); } } } return parentLayer; } LayoutPoint RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, const LayoutPoint& location, ColumnOffsetAdjustment adjustForColumns) const { if (ancestorLayer == this) return location; const RenderLayer* currLayer = this; LayoutPoint locationInLayerCoords = location; while (currLayer && currLayer != ancestorLayer) currLayer = accumulateOffsetTowardsAncestor(currLayer, ancestorLayer, locationInLayerCoords, adjustForColumns); return locationInLayerCoords; } LayoutSize RenderLayer::offsetFromAncestor(const RenderLayer* ancestorLayer, ColumnOffsetAdjustment adjustForColumns) const { return toLayoutSize(convertToLayerCoords(ancestorLayer, LayoutPoint(), adjustForColumns)); } #if PLATFORM(IOS) bool RenderLayer::hasAcceleratedTouchScrolling() const { #if ENABLE(ACCELERATED_OVERFLOW_SCROLLING) if (!scrollsOverflow()) return false; Settings* settings = renderer().document().settings(); // FIXME: settings should not be null at this point. If you find a reliable way to hit this assertion, please file a bug. // See . ASSERT(settings); return renderer().style().useTouchOverflowScrolling() || (settings && settings->alwaysUseAcceleratedOverflowScroll()); #else return false; #endif } bool RenderLayer::hasTouchScrollableOverflow() const { return hasAcceleratedTouchScrolling() && (hasScrollableHorizontalOverflow() || hasScrollableVerticalOverflow()); } #if ENABLE(TOUCH_EVENTS) bool RenderLayer::handleTouchEvent(const PlatformTouchEvent& touchEvent) { // If we have accelerated scrolling, let the scrolling be handled outside of WebKit. if (hasTouchScrollableOverflow()) return false; return ScrollableArea::handleTouchEvent(touchEvent); } #endif #endif // PLATFORM(IOS) #if ENABLE(IOS_TOUCH_EVENTS) void RenderLayer::registerAsTouchEventListenerForScrolling() { if (!renderer().element() || m_registeredAsTouchEventListenerForScrolling) return; renderer().document().addTouchEventListener(renderer().element()); m_registeredAsTouchEventListenerForScrolling = true; } void RenderLayer::unregisterAsTouchEventListenerForScrolling() { if (!renderer().element() || !m_registeredAsTouchEventListenerForScrolling) return; renderer().document().removeTouchEventListener(renderer().element()); m_registeredAsTouchEventListenerForScrolling = false; } #endif // ENABLE(IOS_TOUCH_EVENTS) bool RenderLayer::usesCompositedScrolling() const { return isComposited() && backing()->scrollingLayer(); } bool RenderLayer::usesAsyncScrolling() const { return hasAcceleratedTouchScrolling() && usesCompositedScrolling(); } bool RenderLayer::needsCompositedScrolling() const { return m_needsCompositedScrolling; } void RenderLayer::updateNeedsCompositedScrolling() { bool oldNeedsCompositedScrolling = m_needsCompositedScrolling; if (!renderer().view().frameView().containsScrollableArea(this)) m_needsCompositedScrolling = false; else { bool forceUseCompositedScrolling = acceleratedCompositingForOverflowScrollEnabled() && canBeStackingContainer() && !hasOutOfFlowPositionedDescendant(); #if !PLATFORM(IOS) && ENABLE(ACCELERATED_OVERFLOW_SCROLLING) m_needsCompositedScrolling = forceUseCompositedScrolling || renderer().style().useTouchOverflowScrolling(); #else // On iOS we don't want to opt into accelerated composited scrolling, which creates scroll bar // layers in WebCore, because we use UIKit to composite our scroll bars. m_needsCompositedScrolling = forceUseCompositedScrolling; #endif } if (oldNeedsCompositedScrolling != m_needsCompositedScrolling) { updateSelfPaintingLayer(); if (isStackingContainer()) dirtyZOrderLists(); else clearZOrderLists(); dirtyStackingContainerZOrderLists(); compositor().setShouldReevaluateCompositingAfterLayout(); compositor().setCompositingLayersNeedRebuild(); } } static inline int adjustedScrollDelta(int beginningDelta) { // This implemention matches Firefox's. // http://mxr.mozilla.org/firefox/source/toolkit/content/widgets/browser.xml#856. const int speedReducer = 12; int adjustedDelta = beginningDelta / speedReducer; if (adjustedDelta > 1) adjustedDelta = static_cast(adjustedDelta * sqrt(static_cast(adjustedDelta))) - 1; else if (adjustedDelta < -1) adjustedDelta = static_cast(adjustedDelta * sqrt(static_cast(-adjustedDelta))) + 1; return adjustedDelta; } static inline IntSize adjustedScrollDelta(const IntSize& delta) { return IntSize(adjustedScrollDelta(delta.width()), adjustedScrollDelta(delta.height())); } void RenderLayer::panScrollFromPoint(const IntPoint& sourcePoint) { IntPoint lastKnownMousePosition = renderer().frame().eventHandler().lastKnownMousePosition(); // We need to check if the last known mouse position is out of the window. When the mouse is out of the window, the position is incoherent static IntPoint previousMousePosition; if (lastKnownMousePosition.x() < 0 || lastKnownMousePosition.y() < 0) lastKnownMousePosition = previousMousePosition; else previousMousePosition = lastKnownMousePosition; IntSize delta = lastKnownMousePosition - sourcePoint; if (abs(delta.width()) <= ScrollView::noPanScrollRadius) // at the center we let the space for the icon delta.setWidth(0); if (abs(delta.height()) <= ScrollView::noPanScrollRadius) delta.setHeight(0); scrollByRecursively(adjustedScrollDelta(delta), ScrollOffsetClamped); } // FIXME: unify with the scrollRectToVisible() code below. void RenderLayer::scrollByRecursively(const IntSize& delta, ScrollOffsetClamping clamp, ScrollableArea** scrolledArea) { if (delta.isZero()) return; bool restrictedByLineClamp = false; if (renderer().parent()) restrictedByLineClamp = !renderer().parent()->style().lineClamp().isNone(); if (renderer().hasOverflowClip() && !restrictedByLineClamp) { ScrollOffset newScrollOffset = scrollOffset() + delta; scrollToOffset(newScrollOffset, clamp); if (scrolledArea) *scrolledArea = this; // If this layer can't do the scroll we ask the next layer up that can scroll to try IntSize remainingScrollOffset = newScrollOffset - scrollOffset(); if (!remainingScrollOffset.isZero() && renderer().parent()) { if (RenderLayer* scrollableLayer = enclosingScrollableLayer()) scrollableLayer->scrollByRecursively(remainingScrollOffset, clamp, scrolledArea); renderer().frame().eventHandler().updateAutoscrollRenderer(); } } else { // If we are here, we were called on a renderer that can be programmatically scrolled, but doesn't // have an overflow clip. Which means that it is a document node that can be scrolled. renderer().view().frameView().scrollBy(delta); if (scrolledArea) *scrolledArea = &renderer().view().frameView(); // FIXME: If we didn't scroll the whole way, do we want to try looking at the frames ownerElement? // https://bugs.webkit.org/show_bug.cgi?id=28237 } } void RenderLayer::scrollToXPosition(int x, ScrollOffsetClamping clamp) { ScrollPosition position(x, m_scrollPosition.y()); scrollToOffset(scrollOffsetFromPosition(position), clamp); } void RenderLayer::scrollToYPosition(int y, ScrollOffsetClamping clamp) { ScrollPosition position(m_scrollPosition.x(), y); scrollToOffset(scrollOffsetFromPosition(position), clamp); } ScrollOffset RenderLayer::clampScrollOffset(const ScrollOffset& scrollOffset) const { return scrollOffset.constrainedBetween(IntPoint(), maximumScrollOffset()); } void RenderLayer::scrollToOffset(const ScrollOffset& scrollOffset, ScrollOffsetClamping clamp) { ScrollOffset newScrollOffset = clamp == ScrollOffsetClamped ? clampScrollOffset(scrollOffset) : scrollOffset; if (newScrollOffset != this->scrollOffset()) scrollToOffsetWithoutAnimation(newScrollOffset); } void RenderLayer::scrollTo(const ScrollPosition& position) { RenderBox* box = renderBox(); if (!box) return; LOG_WITH_STREAM(Scrolling, stream << "RenderLayer::scrollTo " << position); ScrollPosition newPosition = position; if (box->style().overflowX() != OMARQUEE) { // Ensure that the dimensions will be computed if they need to be (for overflow:hidden blocks). if (m_scrollDimensionsDirty) computeScrollDimensions(); #if PLATFORM(IOS) if (adjustForIOSCaretWhenScrolling()) { // FIXME: It's not clear what this code is trying to do. Behavior seems reasonable with it removed. int maxOffset = scrollWidth() - box->clientWidth(); ScrollOffset newOffset = scrollOffsetFromPosition(newPosition); int scrollXOffset = newOffset.x(); if (scrollXOffset > maxOffset - caretWidth) { scrollXOffset += caretWidth; if (scrollXOffset <= caretWidth) scrollXOffset = 0; } else if (scrollXOffset < m_scrollPosition.x() - caretWidth) scrollXOffset -= caretWidth; newOffset.setX(scrollXOffset); newPosition = scrollPositionFromOffset(newOffset); } #endif } if (m_scrollPosition == newPosition) { #if PLATFORM(IOS) if (m_requiresScrollBoundsOriginUpdate) updateCompositingLayersAfterScroll(); #endif return; } ScrollPosition oldPosition = IntPoint(m_scrollPosition); m_scrollPosition = newPosition; RenderView& view = renderer().view(); // Update the positions of our child layers (if needed as only fixed layers should be impacted by a scroll). // We don't update compositing layers, because we need to do a deep update from the compositing ancestor. if (!view.frameView().isInRenderTreeLayout()) { // If we're in the middle of layout, we'll just update layers once layout has finished. updateLayerPositionsAfterOverflowScroll(); // Update regions, scrolling may change the clip of a particular region. #if ENABLE(DASHBOARD_SUPPORT) view.frameView().updateAnnotatedRegions(); #endif view.frameView().updateWidgetPositions(); if (!m_updatingMarqueePosition) { // Avoid updating compositing layers if, higher on the stack, we're already updating layer // positions. Updating layer positions requires a full walk of up-to-date RenderLayers, and // in this case we're still updating their positions; we'll update compositing layers later // when that completes. updateCompositingLayersAfterScroll(); } #if PLATFORM(IOS) && ENABLE(TOUCH_EVENTS) renderer().document().dirtyTouchEventRects(); #endif DebugPageOverlays::didLayout(renderer().frame()); } Frame& frame = renderer().frame(); RenderLayerModelObject* repaintContainer = renderer().containerForRepaint(); // The caret rect needs to be invalidated after scrolling frame.selection().setCaretRectNeedsUpdate(); FloatQuad quadForFakeMouseMoveEvent = FloatQuad(m_repaintRect); if (repaintContainer) quadForFakeMouseMoveEvent = repaintContainer->localToAbsoluteQuad(quadForFakeMouseMoveEvent); frame.eventHandler().dispatchFakeMouseMoveEventSoonInQuad(quadForFakeMouseMoveEvent); bool requiresRepaint = true; if (compositor().inCompositingMode() && usesCompositedScrolling()) requiresRepaint = false; // Just schedule a full repaint of our object. if (requiresRepaint) renderer().repaintUsingContainer(repaintContainer, m_repaintRect); // Schedule the scroll and scroll-related DOM events. if (Element* element = renderer().element()) { element->document().eventQueue().enqueueOrDispatchScrollEvent(*element); element->document().sendWillRevealEdgeEventsIfNeeded(oldPosition, newPosition, visibleContentRect(), contentsSize(), element); } if (scrollsOverflow()) view.frameView().didChangeScrollOffset(); view.frameView().viewportContentsChanged(); } static inline bool frameElementAndViewPermitScroll(HTMLFrameElementBase* frameElementBase, FrameView& frameView) { // If scrollbars aren't explicitly forbidden, permit scrolling. if (frameElementBase && frameElementBase->scrollingMode() != ScrollbarAlwaysOff) return true; // If scrollbars are forbidden, user initiated scrolls should obviously be ignored. if (frameView.wasScrolledByUser()) return false; // Forbid autoscrolls when scrollbars are off, but permits other programmatic scrolls, // like navigation to an anchor. return !frameView.frame().eventHandler().autoscrollInProgress(); } bool RenderLayer::allowsCurrentScroll() const { if (!renderer().hasOverflowClip()) return false; // Don't scroll to reveal an overflow layer that is restricted by the -webkit-line-clamp property. // FIXME: Is this still needed? It used to be relevant for Safari RSS. if (renderer().parent() && !renderer().parent()->style().lineClamp().isNone()) return false; RenderBox* box = renderBox(); ASSERT(box); // Only boxes can have overflowClip set. if (renderer().frame().eventHandler().autoscrollInProgress()) { // The "programmatically" here is misleading; this asks whether the box has scrollable overflow, // or is a special case like a form control. return box->canBeProgramaticallyScrolled(); } // Programmatic scrolls can scroll overflow:hidden. return box->hasHorizontalOverflow() || box->hasVerticalOverflow(); } void RenderLayer::scrollRectToVisible(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) { LOG_WITH_STREAM(Scrolling, stream << "Layer " << this << " scrollRectToVisible " << rect); RenderLayer* parentLayer = nullptr; LayoutRect newRect = rect; // We may end up propagating a scroll event. It is important that we suspend events until // the end of the function since they could delete the layer or the layer's renderer(). FrameView& frameView = renderer().view().frameView(); if (renderer().parent()) parentLayer = renderer().parent()->enclosingLayer(); if (allowsCurrentScroll()) { // Don't scroll to reveal an overflow layer that is restricted by the -webkit-line-clamp property. // This will prevent us from revealing text hidden by the slider in Safari RSS. RenderBox* box = renderBox(); ASSERT(box); LayoutRect localExposeRect(box->absoluteToLocalQuad(FloatQuad(FloatRect(rect))).boundingBox()); LayoutRect layerBounds(0, 0, box->clientWidth(), box->clientHeight()); LayoutRect r = getRectToExpose(layerBounds, layerBounds, localExposeRect, alignX, alignY); ScrollOffset clampedScrollOffset = clampScrollOffset(scrollOffset() + toIntSize(roundedIntRect(r).location())); if (clampedScrollOffset != scrollOffset()) { ScrollOffset oldScrollOffset = scrollOffset(); scrollToOffset(clampedScrollOffset); IntSize scrollOffsetDifference = scrollOffset() - oldScrollOffset; localExposeRect.move(-scrollOffsetDifference); newRect = LayoutRect(box->localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox()); } } else if (!parentLayer && renderer().isRenderView()) { HTMLFrameOwnerElement* ownerElement = renderer().document().ownerElement(); if (ownerElement && ownerElement->renderer()) { HTMLFrameElementBase* frameElementBase = nullptr; if (is(*ownerElement)) frameElementBase = downcast(ownerElement); if (frameElementAndViewPermitScroll(frameElementBase, frameView)) { LayoutRect viewRect = frameView.visibleContentRect(LegacyIOSDocumentVisibleRect); LayoutRect exposeRect = getRectToExpose(viewRect, viewRect, rect, alignX, alignY); IntPoint scrollOffset(roundedIntPoint(exposeRect.location())); // Adjust offsets if they're outside of the allowable range. scrollOffset = scrollOffset.constrainedBetween(IntPoint(), IntPoint(frameView.contentsSize())); frameView.setScrollPosition(scrollOffset); if (frameView.safeToPropagateScrollToParent()) { parentLayer = ownerElement->renderer()->enclosingLayer(); // Convert the rect into the coordinate space of the parent frame's document. newRect = frameView.contentsToContainingViewContents(enclosingIntRect(newRect)); } else parentLayer = nullptr; } } else { #if !PLATFORM(IOS) LayoutRect viewRect = frameView.visibleContentRect(); LayoutRect visibleRectRelativeToDocument = viewRect; visibleRectRelativeToDocument.setLocation(frameView.documentScrollPositionRelativeToScrollableAreaOrigin()); #else LayoutRect viewRect = frameView.unobscuredContentRect(); LayoutRect visibleRectRelativeToDocument = viewRect; #endif LayoutRect r = getRectToExpose(viewRect, visibleRectRelativeToDocument, rect, alignX, alignY); frameView.setScrollPosition(roundedIntPoint(r.location())); // This is the outermost view of a web page, so after scrolling this view we // scroll its container by calling Page::scrollRectIntoView. // This only has an effect on the Mac platform in applications // that put web views into scrolling containers, such as Mac OS X Mail. // The canAutoscroll function in EventHandler also knows about this. if (Page* page = frameView.frame().page()) page->chrome().scrollRectIntoView(snappedIntRect(rect)); } } if (parentLayer) parentLayer->scrollRectToVisible(newRect, alignX, alignY); } void RenderLayer::updateCompositingLayersAfterScroll() { if (compositor().inCompositingMode()) { // Our stacking container is guaranteed to contain all of our descendants that may need // repositioning, so update compositing layers from there. if (RenderLayer* compositingAncestor = stackingContainer()->enclosingCompositingLayer()) { if (usesCompositedScrolling() && !hasOutOfFlowPositionedDescendant()) compositor().updateCompositingLayers(CompositingUpdateOnCompositedScroll, compositingAncestor); else compositor().updateCompositingLayers(CompositingUpdateOnScroll, compositingAncestor); } } } LayoutRect RenderLayer::getRectToExpose(const LayoutRect &visibleRect, const LayoutRect &visibleRectRelativeToDocument, const LayoutRect &exposeRect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) { // Determine the appropriate X behavior. ScrollBehavior scrollX; LayoutRect exposeRectX(exposeRect.x(), visibleRect.y(), exposeRect.width(), visibleRect.height()); LayoutUnit intersectWidth = intersection(visibleRect, exposeRectX).width(); if (intersectWidth == exposeRect.width() || intersectWidth >= MIN_INTERSECT_FOR_REVEAL) // If the rectangle is fully visible, use the specified visible behavior. // If the rectangle is partially visible, but over a certain threshold, // then treat it as fully visible to avoid unnecessary horizontal scrolling scrollX = ScrollAlignment::getVisibleBehavior(alignX); else if (intersectWidth == visibleRect.width()) { // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. scrollX = ScrollAlignment::getVisibleBehavior(alignX); if (scrollX == alignCenter) scrollX = noScroll; } else if (intersectWidth > 0) // If the rectangle is partially visible, but not above the minimum threshold, use the specified partial behavior scrollX = ScrollAlignment::getPartialBehavior(alignX); else scrollX = ScrollAlignment::getHiddenBehavior(alignX); // If we're trying to align to the closest edge, and the exposeRect is further right // than the visibleRect, and not bigger than the visible area, then align with the right. if (scrollX == alignToClosestEdge && exposeRect.maxX() > visibleRect.maxX() && exposeRect.width() < visibleRect.width()) scrollX = alignRight; // Given the X behavior, compute the X coordinate. LayoutUnit x; if (scrollX == noScroll) x = visibleRect.x(); else if (scrollX == alignRight) x = exposeRect.maxX() - visibleRect.width(); else if (scrollX == alignCenter) x = exposeRect.x() + (exposeRect.width() - visibleRect.width()) / 2; else x = exposeRect.x(); // Determine the appropriate Y behavior. ScrollBehavior scrollY; LayoutRect exposeRectY(visibleRect.x(), exposeRect.y(), visibleRect.width(), exposeRect.height()); LayoutUnit intersectHeight = intersection(visibleRectRelativeToDocument, exposeRectY).height(); if (intersectHeight == exposeRect.height()) // If the rectangle is fully visible, use the specified visible behavior. scrollY = ScrollAlignment::getVisibleBehavior(alignY); else if (intersectHeight == visibleRect.height()) { // If the rect is bigger than the visible area, don't bother trying to center. Other alignments will work. scrollY = ScrollAlignment::getVisibleBehavior(alignY); if (scrollY == alignCenter) scrollY = noScroll; } else if (intersectHeight > 0) // If the rectangle is partially visible, use the specified partial behavior scrollY = ScrollAlignment::getPartialBehavior(alignY); else scrollY = ScrollAlignment::getHiddenBehavior(alignY); // If we're trying to align to the closest edge, and the exposeRect is further down // than the visibleRect, and not bigger than the visible area, then align with the bottom. if (scrollY == alignToClosestEdge && exposeRect.maxY() > visibleRect.maxY() && exposeRect.height() < visibleRect.height()) scrollY = alignBottom; // Given the Y behavior, compute the Y coordinate. LayoutUnit y; if (scrollY == noScroll) y = visibleRect.y(); else if (scrollY == alignBottom) y = exposeRect.maxY() - visibleRect.height(); else if (scrollY == alignCenter) y = exposeRect.y() + (exposeRect.height() - visibleRect.height()) / 2; else y = exposeRect.y(); return LayoutRect(LayoutPoint(x, y), visibleRect.size()); } void RenderLayer::autoscroll(const IntPoint& position) { IntPoint currentDocumentPosition = renderer().view().frameView().windowToContents(position); scrollRectToVisible(LayoutRect(currentDocumentPosition, LayoutSize(1, 1)), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); } bool RenderLayer::canResize() const { // We need a special case for