/* * 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 * 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 "ColumnInfo.h" #include "CSSPropertyNames.h" #include "Chrome.h" #include "Document.h" #include "DocumentEventQueue.h" #include "EventHandler.h" #include "FeatureObserver.h" #include "FloatConversion.h" #include "FloatPoint3D.h" #include "FloatRect.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 "HTMLFrameElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLNames.h" #include "HistogramSupport.h" #include "HitTestingTransformState.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "OverflowEvent.h" #include "OverlapTestRequestClient.h" #include "Page.h" #include "PlatformMouseEvent.h" #include "RenderArena.h" #include "RenderFlowThread.h" #include "RenderGeometryMap.h" #include "RenderInline.h" #include "RenderMarquee.h" #include "RenderReplica.h" #include "RenderSVGResourceClipper.h" #include "RenderScrollbar.h" #include "RenderScrollbarPart.h" #include "RenderTheme.h" #include "RenderTreeAsText.h" #include "RenderView.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 "StylePropertySet.h" #include "StyleResolver.h" #include "TextStream.h" #include "TransformationMatrix.h" #include "TranslateTransformOperation.h" #include #include #if ENABLE(CSS_FILTERS) #include "FEColorMatrix.h" #include "FEMerge.h" #include "FilterEffectRenderer.h" #include "RenderLayerFilterInfo.h" #endif #if USE(ACCELERATED_COMPOSITING) #include "RenderLayerBacking.h" #include "RenderLayerCompositor.h" #endif #if ENABLE(SVG) #include "SVGNames.h" #endif #if ENABLE(CSS_SHADERS) && USE(3D_GRAPHICS) #include "CustomFilterGlobalContext.h" #include "CustomFilterOperation.h" #include "CustomFilterValidatedProgram.h" #include "ValidatedCustomFilterOperation.h" #endif #define MIN_INTERSECT_FOR_REVEAL 32 using namespace std; namespace WebCore { using namespace HTMLNames; const int MinimumWidthWhileResizing = 100; const int MinimumHeightWhileResizing = 40; bool ClipRect::intersects(const HitTestLocation& hitTestLocation) const { return hitTestLocation.intersects(m_rect); } void makeMatrixRenderable(TransformationMatrix& matrix, bool has3DRendering) { #if !ENABLE(3D_RENDERING) UNUSED_PARAM(has3DRendering); matrix.makeAffine(); #else if (!has3DRendering) matrix.makeAffine(); #endif } RenderLayer::RenderLayer(RenderLayerModelObject* renderer) : 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_isRootLayer(renderer->isRenderView()) , 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_isPaginated(false) , m_3DTransformedDescendantStatusDirty(true) , m_has3DTransformedDescendant(false) #if USE(ACCELERATED_COMPOSITING) , m_hasCompositingDescendant(false) , m_indirectCompositingReason(NoIndirectCompositingReason) , m_viewportConstrainedNotCompositedReason(NoNotCompositedReason) #endif , m_containsDirtyOverlayScrollbars(false) , m_updatingMarqueePosition(false) #if !ASSERT_DISABLED , m_layerListMutationAllowed(true) #endif #if ENABLE(CSS_FILTERS) , m_hasFilterInfo(false) #endif #if ENABLE(CSS_COMPOSITING) , m_blendMode(BlendModeNormal) #endif , m_renderer(renderer) , m_parent(0) , m_previous(0) , m_next(0) , m_first(0) , m_last(0) , m_staticInlinePosition(0) , m_staticBlockPosition(0) , m_reflection(0) , m_scrollCorner(0) , m_resizer(0) , m_enclosingPaginationLayer(0) { 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(); ScrollableArea::setConstrainsScrollingToContentEdge(false); if (!renderer->firstChild() && renderer->style()) { m_visibleContentStatusDirty = false; m_hasVisibleContent = renderer->style()->visibility() == VISIBLE; } Node* node = renderer->node(); if (node && node->isElementNode()) { // We save and restore only the scrollOffset as the other scroll values are recalculated. Element* element = toElement(node); m_scrollOffset = element->savedLayerScrollOffset(); if (!m_scrollOffset.isZero()) scrollAnimator()->setCurrentPosition(FloatPoint(m_scrollOffset.width(), m_scrollOffset.height())); element->setSavedLayerScrollOffset(IntSize()); } } RenderLayer::~RenderLayer() { if (inResizeMode() && !renderer()->documentBeingDestroyed()) { if (Frame* frame = renderer()->frame()) frame->eventHandler()->resizeLayerDestroyed(); } if (Frame* frame = renderer()->frame()) { if (FrameView* frameView = frame->view()) frameView->removeScrollableArea(this); } if (!m_renderer->documentBeingDestroyed()) { Node* node = m_renderer->node(); if (node && node->isElementNode()) toElement(node)->setSavedLayerScrollOffset(m_scrollOffset); } destroyScrollbar(HorizontalScrollbar); destroyScrollbar(VerticalScrollbar); if (renderer()->frame() && renderer()->frame()->page()) { if (ScrollingCoordinator* scrollingCoordinator = renderer()->frame()->page()->scrollingCoordinator()) scrollingCoordinator->willDestroyScrollableArea(this); } if (m_reflection) removeReflection(); #if ENABLE(CSS_FILTERS) removeFilterInfoIfNeeded(); #endif // Child layers will be deleted by their corresponding render objects, so // we don't need to delete them ourselves. #if USE(ACCELERATED_COMPOSITING) clearBacking(true); #endif if (m_scrollCorner) m_scrollCorner->destroy(); if (m_resizer) m_resizer->destroy(); } String RenderLayer::name() const { StringBuilder name; name.append(renderer()->renderName()); if (Element* element = renderer()->node() && renderer()->node()->isElementNode() ? toElement(renderer()->node()) : 0) { 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(); } #if USE(ACCELERATED_COMPOSITING) RenderLayerCompositor* RenderLayer::compositor() const { if (!renderer()->view()) return 0; return renderer()->view()->compositor(); } void RenderLayer::contentChanged(ContentChangeType changeType) { // This can get called when video becomes accelerated, so the layers may change. if ((changeType == CanvasChanged || changeType == VideoChanged || changeType == FullScreenChanged) && compositor()->updateLayerCompositingState(this)) compositor()->setCompositingLayersNeedRebuild(); if (m_backing) m_backing->contentChanged(changeType); } #endif // USE(ACCELERATED_COMPOSITING) bool RenderLayer::canRender3DTransforms() const { #if USE(ACCELERATED_COMPOSITING) return compositor()->canRender3DTransforms(); #else return false; #endif } #if ENABLE(CSS_FILTERS) bool RenderLayer::paintsWithFilters() const { // FIXME: Eventually there will be more factors than isComposited() to decide whether or not to render the filter if (!renderer()->hasFilter()) return false; #if USE(ACCELERATED_COMPOSITING) if (!isComposited()) return true; if (!m_backing || !m_backing->canCompositeFilters()) return true; #endif return false; } bool RenderLayer::requiresFullLayerImageForFilters() const { if (!paintsWithFilters()) return false; FilterEffectRenderer* filter = filterRenderer(); return filter ? filter->hasFilterThatMovesPixels() : false; } FilterEffectRenderer* RenderLayer::filterRenderer() const { RenderLayerFilterInfo* filterInfo = this->filterInfo(); return filterInfo ? filterInfo->renderer() : 0; } RenderLayerFilterInfo* RenderLayer::filterInfo() const { return hasFilterInfo() ? RenderLayerFilterInfo::filterInfoForRenderLayer(this) : 0; } RenderLayerFilterInfo* RenderLayer::ensureFilterInfo() { return RenderLayerFilterInfo::createFilterInfoForRenderLayerIfNeeded(this); } void RenderLayer::removeFilterInfoIfNeeded() { if (hasFilterInfo()) RenderLayerFilterInfo::removeFilterInfoForRenderLayer(this); } #endif LayoutPoint RenderLayer::computeOffsetFromRoot(bool& hasLayerOffset) const { hasLayerOffset = true; if (!parent()) return LayoutPoint(); // This is similar to root() but we check if an ancestor layer would // prevent the optimization from working. const RenderLayer* rootLayer = 0; for (const RenderLayer* parentLayer = parent(); parentLayer; rootLayer = parentLayer, parentLayer = parentLayer->parent()) { hasLayerOffset = parentLayer->canUseConvertToLayerCoords(); if (!hasLayerOffset) return LayoutPoint(); } ASSERT(rootLayer == root()); LayoutPoint offset; parent()->convertToLayerCoords(rootLayer, offset); return offset; } void RenderLayer::updateLayerPositionsAfterLayout(const RenderLayer* rootLayer, UpdateLayerPositionsFlags flags) { RenderGeometryMap geometryMap(UseTransforms); if (this != rootLayer) geometryMap.pushMappingsToAncestor(parent(), 0); 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()) { LayoutPoint offsetFromRoot; if (geometryMap) offsetFromRoot = LayoutPoint(geometryMap->absolutePoint(FloatPoint())); else { // FIXME: It looks suspicious to call convertToLayerCoords here // as canUseConvertToLayerCoords may be true for an ancestor layer. convertToLayerCoords(root(), offsetFromRoot); } positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot))); } updateDescendantDependentFlags(); if (flags & UpdatePagination) updatePagination(); else { m_isPaginated = false; m_enclosingPaginationLayer = 0; } if (m_hasVisibleContent) { RenderView* view = renderer()->view(); ASSERT(view); // 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(!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 (view && !view->printing()) { if (m_repaintStatus & NeedsFullRepaint) { renderer()->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(oldRepaintRect)); if (m_repaintRect != oldRepaintRect) renderer()->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(m_repaintRect)); } else if (shouldRepaintAfterLayout()) renderer()->repaintAfterLayoutIfNeeded(repaintContainer, oldRepaintRect, oldOutlineBox, &m_repaintRect, &m_outlineBox); } } } else clearRepaintRects(); m_repaintStatus = NeedsNormalRepaint; // Go ahead and update the reflection's position and size. if (m_reflection) m_reflection->layout(); #if USE(ACCELERATED_COMPOSITING) // Clear the IsCompositingUpdateRoot flag once we've found the first compositing layer in this update. bool isUpdateRoot = (flags & IsCompositingUpdateRoot); if (isComposited()) flags &= ~IsCompositingUpdateRoot; #endif if (useRegionBasedColumns() && renderer()->isInFlowRenderFlowThread()) { updatePagination(); flags |= UpdatePagination; } if (renderer()->hasColumns()) flags |= UpdatePagination; for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) child->updateLayerPositions(geometryMap, flags); #if USE(ACCELERATED_COMPOSITING) if ((flags & UpdateCompositingLayers) && isComposited()) { RenderLayerBacking::UpdateAfterLayoutFlags updateFlags = RenderLayerBacking::CompositingChildrenOnly; if (flags & NeedsFullRepaintInBacking) updateFlags |= RenderLayerBacking::NeedsFullRepaint; if (isUpdateRoot) updateFlags |= RenderLayerBacking::IsUpdateRoot; backing()->updateAfterLayout(updateFlags); } #endif // 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()); } 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() && renderer()->frame()->page() && renderer()->frame()->page()->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); OwnPtr > posZOrderList; OwnPtr > 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; #if USE(ACCELERATED_COMPOSITING) bool didUpdate = newValue != m_descendantsAreContiguousInStackingOrder; #endif m_descendantsAreContiguousInStackingOrder = newValue; #if USE(ACCELERATED_COMPOSITING) if (didUpdate) updateNeedsCompositedScrolling(); #endif } } 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 = IntRect(); m_outlineBox = IntRect(); } void RenderLayer::updateLayerPositionsAfterDocumentScroll() { ASSERT(this == renderer()->view()->layer()); RenderGeometryMap geometryMap(UseTransforms); updateLayerPositionsAfterScroll(&geometryMap); } void RenderLayer::updateLayerPositionsAfterOverflowScroll() { RenderGeometryMap geometryMap(UseTransforms); RenderView* view = renderer()->view(); if (this != view->layer()) geometryMap.pushMappingsToAncestor(parent(), 0); // 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()); } #if USE(ACCELERATED_COMPOSITING) void RenderLayer::positionNewlyCreatedOverflowControls() { if (!backing()->hasUnpositionedOverflowControlsLayers()) return; RenderGeometryMap geometryMap(UseTransforms); RenderView* view = renderer()->view(); if (this != view->layer() && parent()) geometryMap.pushMappingsToAncestor(parent(), 0); LayoutPoint offsetFromRoot = LayoutPoint(geometryMap.absolutePoint(FloatPoint())); positionOverflowControls(toIntSize(roundedIntPoint(offsetFromRoot))); } #endif #if ENABLE(CSS_COMPOSITING) void RenderLayer::updateBlendMode() { BlendMode newBlendMode = renderer()->style()->blendMode(); if (newBlendMode != m_blendMode) { m_blendMode = newBlendMode; if (backing()) backing()->setBlendMode(newBlendMode); } } #endif void RenderLayer::updateTransform() { // hasTransform() on the renderer is also true when there is transform-style: preserve-3d or perspective set, // so check style too. bool hasTransform = renderer()->hasTransform() && renderer()->style()->hasTransform(); bool had3DTransform = has3DTransform(); bool hadTransform = m_transform; if (hasTransform != hadTransform) { if (hasTransform) m_transform = adoptPtr(new TransformationMatrix); else m_transform.clear(); // Layers with transforms act as clip rects roots, so clear the cached clip rects here. clearClipRectsIncludingDescendants(); } if (hasTransform) { RenderBox* box = renderBox(); ASSERT(box); m_transform->makeIdentity(); box->style()->applyTransform(*m_transform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::IncludeTransformOrigin); makeMatrixRenderable(*m_transform, canRender3DTransforms()); } if (had3DTransform != has3DTransform()) dirty3DTransformedDescendantStatus(); } TransformationMatrix RenderLayer::currentTransform(RenderStyle::ApplyTransformOrigin applyOrigin) const { if (!m_transform) return TransformationMatrix(); #if USE(ACCELERATED_COMPOSITING) if (renderer()->style()->isRunningAcceleratedAnimation()) { TransformationMatrix currTransform; RefPtr style = renderer()->animation()->getAnimatedStyleForRenderer(renderer()); style->applyTransform(currTransform, renderBox()->pixelSnappedBorderBoxRect().size(), applyOrigin); makeMatrixRenderable(currTransform, canRender3DTransforms()); return currTransform; } // m_transform includes transform-origin, so we need to recompute the transform here. if (applyOrigin == RenderStyle::ExcludeTransformOrigin) { RenderBox* box = renderBox(); TransformationMatrix currTransform; box->style()->applyTransform(currTransform, box->pixelSnappedBorderBoxRect().size(), RenderStyle::ExcludeTransformOrigin); makeMatrixRenderable(currTransform, canRender3DTransforms()); return currTransform; } #else UNUSED_PARAM(applyOrigin); #endif 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 0; } static bool checkContainingBlockChainForPagination(RenderLayerModelObject* renderer, RenderBox* ancestorColumnsRenderer) { RenderView* view = renderer->view(); RenderLayerModelObject* prevBlock = renderer; RenderBlock* containingBlock; for (containingBlock = renderer->containingBlock(); containingBlock && containingBlock != view && containingBlock != ancestorColumnsRenderer; containingBlock = containingBlock->containingBlock()) prevBlock = containingBlock; // If the columns block wasn't in our containing block chain, then we aren't paginated by it. if (containingBlock != ancestorColumnsRenderer) return false; // If the previous block is absolutely positioned, then we can't be paginated by the columns block. if (prevBlock->isOutOfFlowPositioned()) return false; // Otherwise we are paginated by the columns block. return true; } bool RenderLayer::useRegionBasedColumns() const { const Settings* settings = renderer()->document()->settings(); return settings && settings->regionBasedColumnsEnabled(); } void RenderLayer::updatePagination() { m_isPaginated = false; m_enclosingPaginationLayer = 0; if (isComposited() || !parent()) return; // FIXME: We will have to deal with paginated compositing layers someday. // FIXME: For now the RenderView can't be paginated. Eventually printing will move to a model where it is though. // The main difference between the paginated booleans for the old column code and the new column code // is that each paginated layer has to paint on its own with the new code. There is no // recurring into child layers. This means that the m_isPaginated bits for the new column code can't just be set on // "roots" that get split and paint all their descendants. Instead 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. bool regionBasedColumnsUsed = useRegionBasedColumns(); if (regionBasedColumnsUsed && renderer()->isInFlowRenderFlowThread()) { m_enclosingPaginationLayer = this; return; } if (isNormalFlowOnly()) { if (regionBasedColumnsUsed) { // 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. m_enclosingPaginationLayer = parent()->enclosingPaginationLayer(); if (m_enclosingPaginationLayer && m_enclosingPaginationLayer->hasTransform()) m_enclosingPaginationLayer = 0; } else m_isPaginated = parent()->renderer()->hasColumns(); 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. if (regionBasedColumnsUsed) { RenderView* view = renderer()->view(); RenderBlock* containingBlock; for (containingBlock = renderer()->containingBlock(); containingBlock && containingBlock != view; 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. m_enclosingPaginationLayer = containingBlock->layer()->enclosingPaginationLayer(); if (m_enclosingPaginationLayer && m_enclosingPaginationLayer->hasTransform()) m_enclosingPaginationLayer = 0; return; } } return; } // If we're not normal flow, then we need to look for a multi-column object between us and our stacking container. RenderLayer* ancestorStackingContainer = stackingContainer(); for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { if (curr->renderer()->hasColumns()) { m_isPaginated = checkContainingBlockChainForPagination(renderer(), curr->renderBox()); return; } if (curr == ancestorStackingContainer) 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) { m_hasVisibleDescendant = false; m_hasSelfPaintingLayerDescendant = false; m_hasOutOfFlowPositionedDescendant = false; HashSet childOutOfFlowDescendantContainingBlocks; for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { childOutOfFlowDescendantContainingBlocks.clear(); child->updateDescendantDependentFlags(&childOutOfFlowDescendantContainingBlocks); bool childIsOutOfFlowPositioned = child->renderer() && 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); } bool hasVisibleDescendant = child->m_hasVisibleContent || child->m_hasVisibleDescendant; bool hasSelfPaintingLayerDescendant = child->isSelfPaintingLayer() || child->hasSelfPaintingLayerDescendant(); bool hasOutOfFlowPositionedDescendant = !childOutOfFlowDescendantContainingBlocks.isEmpty(); m_hasVisibleDescendant |= hasVisibleDescendant; m_hasSelfPaintingLayerDescendant |= hasSelfPaintingLayerDescendant; m_hasOutOfFlowPositionedDescendant |= hasOutOfFlowPositionedDescendant; if (m_hasVisibleDescendant && m_hasSelfPaintingLayerDescendant && m_hasOutOfFlowPositionedDescendant) break; } if (outOfFlowDescendantContainingBlocks && renderer()) outOfFlowDescendantContainingBlocks->remove(renderer()); m_visibleDescendantStatusDirty = false; m_hasSelfPaintingLayerDescendantDirty = false; #if USE(ACCELERATED_COMPOSITING) if (m_hasOutOfFlowPositionedDescendantDirty) updateNeedsCompositedScrolling(); #endif m_hasOutOfFlowPositionedDescendantDirty = false; } 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; } if (r->firstChild() && !r->hasLayer()) r = r->firstChild(); else if (r->nextSibling()) r = r->nextSibling(); else { do { r = r->parent(); if (r == renderer()) r = 0; } 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 (unsigned i = 0; i < positiveZOrderList->size(); ++i) m_has3DTransformedDescendant |= positiveZOrderList->at(i)->update3DTransformedDescendantStatus(); } // Now check our negative z-index children. if (Vector* negativeZOrderList = negZOrderList()) { for (unsigned i = 0; i < negativeZOrderList->size(); ++i) m_has3DTransformedDescendant |= negativeZOrderList->at(i)->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() && renderer()->isRenderInline()) { RenderInline* inlineFlow = toRenderInline(renderer()); IntRect lineBox = inlineFlow->linesBoundingBox(); setSize(lineBox.size()); inlineBoundingBoxOffset = toSize(lineBox.location()); localPoint += inlineBoundingBoxOffset; } else if (RenderBox* box = renderBox()) { // FIXME: Is snapping the size really needed here for the RenderBox case? setSize(pixelSnappedIntSize(box->size(), box->location())); localPoint += box->topLeftLocationOffset(); } if (!renderer()->isOutOfFlowPositioned() && renderer()->parent()) { // We must adjust our position by walking up the render tree looking for the // nearest enclosing object with a layer. RenderObject* curr = renderer()->parent(); while (curr && !curr->hasLayer()) { if (curr->isBox() && !curr->isTableRow()) { // Rows and cells share the same coordinate space (that of the section). // Omit them when computing our xpos/ypos. localPoint += toRenderBox(curr)->topLeftLocationOffset(); } curr = curr->parent(); } if (curr->isBox() && curr->isTableRow()) { // Put ourselves into the row coordinate space. localPoint -= toRenderBox(curr)->topLeftLocationOffset(); } } // Subtract our parent's scroll offset. if (renderer()->isOutOfFlowPositioned() && enclosingPositionedAncestor()) { RenderLayer* positionedParent = enclosingPositionedAncestor(); // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. if (positionedParent->renderer()->hasOverflowClip()) { LayoutSize offset = positionedParent->scrolledContentOffset(); localPoint -= offset; } if (renderer()->isOutOfFlowPositioned() && positionedParent->renderer()->isInFlowPositioned() && positionedParent->renderer()->isRenderInline()) { LayoutSize offset = toRenderInline(positionedParent->renderer())->offsetForInFlowPositionedInline(toRenderBox(renderer())); localPoint += offset; } } else if (parent()) { if (parent()->renderer()->hasOverflowClip()) { IntSize scrollOffset = parent()->scrolledContentOffset(); localPoint -= scrollOffset; } } bool positionOrOffsetChanged = false; if (renderer()->isInFlowPositioned()) { LayoutSize newOffset = toRenderBoxModelObject(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 { if (!renderer()->hasTransform()) return TransformationMatrix(); RenderStyle* style = renderer()->style(); if (!style->hasPerspective()) return TransformationMatrix(); // Maybe fetch the perspective from the backing? const IntRect borderBox = toRenderBox(renderer())->pixelSnappedBorderBoxRect(); const float boxWidth = borderBox.width(); const float boxHeight = borderBox.height(); float perspectiveOriginX = floatValueForLength(style->perspectiveOriginX(), boxWidth); float perspectiveOriginY = floatValueForLength(style->perspectiveOriginY(), boxHeight); // A perspective origin of 0,0 makes the vanishing point in the center of the element. // We want it to be in the top-left, so subtract half the height and width. perspectiveOriginX -= boxWidth / 2.0f; perspectiveOriginY -= boxHeight / 2.0f; TransformationMatrix t; t.translate(perspectiveOriginX, perspectiveOriginY); t.applyPerspective(style->perspective()); t.translate(-perspectiveOriginX, -perspectiveOriginY); return t; } FloatPoint RenderLayer::perspectiveOrigin() const { if (!renderer()->hasTransform()) return FloatPoint(); const LayoutRect borderBox = toRenderBox(renderer())->borderBoxRect(); RenderStyle* style = renderer()->style(); return FloatPoint(floatValueForLength(style->perspectiveOriginX(), borderBox.width()), 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 isPositionedContainer(RenderLayer* layer) { RenderLayerModelObject* layerRenderer = layer->renderer(); return layer->isRootLayer() || layerRenderer->isPositioned() || layer->hasTransform(); } static inline bool isFixedPositionedContainer(RenderLayer* layer) { return layer->isRootLayer() || layer->hasTransform(); } RenderLayer* RenderLayer::enclosingPositionedAncestor() const { RenderLayer* curr = parent(); while (curr && !isPositionedContainer(curr)) curr = curr->parent(); return curr; } RenderLayer* RenderLayer::enclosingScrollableLayer() const { for (RenderObject* nextRenderer = renderer()->parent(); nextRenderer; nextRenderer = nextRenderer->parent()) { if (nextRenderer->isBox() && toRenderBox(nextRenderer)->canBeScrolledAndHasScrollableArea()) return nextRenderer->enclosingLayer(); } return 0; } IntRect RenderLayer::scrollableAreaBoundingBox() const { return renderer()->absoluteBoundingBoxRect(); } bool RenderLayer::scrollbarAnimationsAreSuppressed() const { RenderView* view = renderer()->view(); if (!view) return false; return view->frameView()->scrollbarAnimationsAreSuppressed(); } 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 USE(ACCELERATED_COMPOSITING) 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(); #else return true; #endif } #if USE(ACCELERATED_COMPOSITING) 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 0; } RenderLayer* RenderLayer::enclosingCompositingLayerForRepaint(IncludeSelfOrNot includeSelf) const { if (includeSelf == IncludeSelf && isComposited() && !backing()->paintsIntoCompositedAncestor()) return const_cast(this); for (const RenderLayer* curr = compositingContainer(this); curr; curr = compositingContainer(curr)) { if (curr->isComposited() && !curr->backing()->paintsIntoCompositedAncestor()) return const_cast(curr); } return 0; } #endif #if ENABLE(CSS_FILTERS) 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 0; } RenderLayer* RenderLayer::enclosingFilterRepaintLayer() const { for (const RenderLayer* curr = this; curr; curr = curr->parent()) { if ((curr != this && curr->requiresFullLayerImageForFilters()) || curr->isComposited() || curr->isRootLayer()) return const_cast(curr); } return 0; } void RenderLayer::setFilterBackendNeedsRepaintingInRect(const LayoutRect& rect, bool immediate) { if (rect.isEmpty()) return; LayoutRect rectForRepaint = rect; renderer()->style()->filterOutsets().expandRect(rectForRepaint); RenderLayerFilterInfo* filterInfo = this->filterInfo(); ASSERT(filterInfo); filterInfo->expandDirtySourceRect(rectForRepaint); #if ENABLE(CSS_SHADERS) ASSERT(filterInfo->renderer()); if (filterInfo->renderer()->hasCustomShaderFilter()) { // If we have at least one custom shader, we need to update the whole bounding box of the layer, because the // shader can address any ouput pixel. // Note: This is only for output rect, so there's no need to expand the dirty source rect. rectForRepaint.unite(calculateLayerBounds(this)); } #endif RenderLayer* parentLayer = enclosingFilterRepaintLayer(); ASSERT(parentLayer); FloatQuad repaintQuad(rectForRepaint); LayoutRect parentLayerRect = renderer()->localToContainerQuad(repaintQuad, parentLayer->renderer()).enclosingBoundingBox(); #if USE(ACCELERATED_COMPOSITING) 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(); } #endif if (parentLayer->paintsWithFilters()) { parentLayer->setFilterBackendNeedsRepaintingInRect(parentLayerRect, immediate); return; } if (parentLayer->isRootLayer()) { RenderView* view = toRenderView(parentLayer->renderer()); view->repaintViewRectangle(parentLayerRect, immediate); return; } ASSERT_NOT_REACHED(); } bool RenderLayer::hasAncestorWithFilterOutsets() const { for (const RenderLayer* curr = this; curr; curr = curr->parent()) { RenderLayerModelObject* renderer = curr->renderer(); if (renderer->style()->hasFilterOutsets()) return true; } return false; } #endif RenderLayer* RenderLayer::clippingRootForPainting() const { #if USE(ACCELERATED_COMPOSITING) if (isComposited()) return const_cast(this); #endif const RenderLayer* current = this; while (current) { if (current->isRootLayer()) return const_cast(current); current = compositingContainer(current); ASSERT(current); if (current->transform() #if USE(ACCELERATED_COMPOSITING) || (current->isComposited() && !current->backing()->paintsIntoCompositedAncestor()) #endif ) return const_cast(current); } ASSERT_NOT_REACHED(); return 0; } LayoutPoint RenderLayer::absoluteToContents(const LayoutPoint& absolutePoint) const { // We don't use convertToLayerCoords because it doesn't know about transforms return roundedLayoutPoint(renderer()->absoluteToLocal(absolutePoint, UseTransforms)); } bool RenderLayer::cannotBlitToWindow() const { if (isTransparent() || hasReflection() || hasTransform()) return true; if (!parent()) return false; return parent()->cannotBlitToWindow(); } bool RenderLayer::isTransparent() const { #if ENABLE(SVG) if (renderer()->node() && renderer()->node()->namespaceURI() == SVGNames::svgNamespaceURI) return false; #endif return renderer()->isTransparent() || renderer()->hasMask(); } RenderLayer* RenderLayer::transparentPaintingAncestor() { if (isComposited()) return 0; for (RenderLayer* curr = parent(); curr; curr = curr->parent()) { if (curr->isComposited()) return 0; if (curr->isTransparent()) return curr; } return 0; } enum TransparencyClipBoxBehavior { PaintingTransparencyClipBox, HitTestingTransparencyClipBox }; enum TransparencyClipBoxMode { DescendantsOfTransparencyClipBox, RootOfTransparencyClipBox }; static LayoutRect transparencyClipBox(const RenderLayer*, const RenderLayer* rootLayer, TransparencyClipBoxBehavior, TransparencyClipBoxMode, PaintBehavior = 0); 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)); } } // 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()) { LayoutPoint delta; layer->convertToLayerCoords(rootLayer, delta); clipRect.move(-delta.x(), -delta.y()); clipRect.unite(layer->renderBox()->reflectedRect(clipRect)); clipRect.moveBy(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. const RenderLayer* paginationLayer = transparencyMode == DescendantsOfTransparencyClipBox ? layer->enclosingPaginationLayer() : 0; const RenderLayer* rootLayerForTransform = paginationLayer ? paginationLayer : rootLayer; LayoutPoint delta; layer->convertToLayerCoords(rootLayerForTransform, delta); TransformationMatrix transform; transform.translate(delta.x(), delta.y()); transform = transform * *layer->transform(); // We don't use fragment boxes when collecting a transformed layer's bounding box, since it always // paints unfragmented. LayoutRect clipRect = layer->boundingBox(layer); expandClipRectForDescendantsAndReflection(clipRect, layer, layer, transparencyBehavior, paintBehavior); #if ENABLE(CSS_FILTERS) layer->renderer()->style()->filterOutsets().expandRect(clipRect); #endif 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. RenderFlowThread* enclosingFlowThread = toRenderFlowThread(paginationLayer->renderer()); result = enclosingFlowThread->fragmentsBoundingBox(result); LayoutPoint rootLayerDelta; paginationLayer->convertToLayerCoords(rootLayer, rootLayerDelta); result.moveBy(rootLayerDelta); return result; } LayoutRect clipRect = layer->boundingBox(rootLayer, RenderLayer::UseFragmentBoxes); expandClipRectForDescendantsAndReflection(clipRect, layer, rootLayer, transparencyBehavior, paintBehavior); #if ENABLE(CSS_FILTERS) layer->renderer()->style()->filterOutsets().expandRect(clipRect); #endif return clipRect; } LayoutRect RenderLayer::paintingExtent(const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, PaintBehavior paintBehavior) { return intersection(transparencyClipBox(this, rootLayer, PaintingTransparencyClipBox, RootOfTransparencyClipBox, paintBehavior), paintDirtyRect); } void RenderLayer::beginTransparencyLayers(GraphicsContext* context, const RenderLayer* rootLayer, const LayoutRect& paintDirtyRect, PaintBehavior paintBehavior) { if (context->paintingDisabled() || (paintsWithTransparency(paintBehavior) && m_usedTransparency)) return; RenderLayer* ancestor = transparentPaintingAncestor(); if (ancestor) ancestor->beginTransparencyLayers(context, rootLayer, paintDirtyRect, paintBehavior); if (paintsWithTransparency(paintBehavior)) { m_usedTransparency = true; context->save(); LayoutRect clipRect = paintingExtent(rootLayer, paintDirtyRect, paintBehavior); context->clip(clipRect); context->beginTransparencyLayer(renderer()->opacity()); #ifdef REVEAL_TRANSPARENCY_LAYERS context->setFillColor(Color(0.0f, 0.0f, 0.5f, 0.2f), ColorSpaceDeviceRGB); context->fillRect(clipRect); #endif } } void* RenderLayer::operator new(size_t sz, RenderArena* renderArena) { return renderArena->allocate(sz); } void RenderLayer::operator delete(void* ptr, size_t sz) { // Stash size where destroy can find it. *(size_t *)ptr = sz; } void RenderLayer::destroy(RenderArena* renderArena) { delete this; // Recover the size left there for us by operator delete and free the memory. renderArena->free(*(size_t *)this, this); } 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() && (child->renderer()->isOutOfFlowPositioned() || child->hasOutOfFlowPositionedDescendant())) setAncestorChainHasOutOfFlowPositionedDescendant(child->renderer()->containingBlock()); #if USE(ACCELERATED_COMPOSITING) compositor()->layerWasAdded(this, child); #endif } RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild) { #if USE(ACCELERATED_COMPOSITING) if (!renderer()->documentBeingDestroyed()) compositor()->layerWillBeRemoved(this, oldChild); #endif // 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() && oldChild->renderer()->isOutOfFlowPositioned()) || oldChild->hasOutOfFlowPositionedDescendant()) dirtyAncestorChainHasOutOfFlowPositionedDescendantStatus(); oldChild->setPreviousSibling(0); oldChild->setNextSibling(0); oldChild->setParent(0); oldChild->updateDescendantDependentFlags(); if (oldChild->m_hasVisibleContent || oldChild->m_hasVisibleDescendant) dirtyAncestorChainVisibleDescendantStatus(); if (oldChild->isSelfPaintingLayer() || oldChild->hasSelfPaintingLayerDescendant()) dirtyAncestorChainHasSelfPaintingLayerDescendantStatus(); 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. m_renderer->setHasLayer(false); #if USE(ACCELERATED_COMPOSITING) compositor()->layerWillBeRemoved(m_parent, this); #endif // 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(0); // FIXME: use geometry map. current = next; } // Remove us from the parent. m_parent->removeChild(this); m_renderer->destroyLayer(); } void RenderLayer::insertOnlyThisLayer() { if (!m_parent && renderer()->parent()) { // We need to connect ourselves when our renderer() has a parent. // Find our enclosingLayer and add ourselves. RenderLayer* parentLayer = renderer()->parent()->enclosingLayer(); ASSERT(parentLayer); RenderLayer* beforeChild = parentLayer->reflectionLayer() != this ? renderer()->parent()->findNextLayer(parentLayer, renderer()) : 0; parentLayer->addChild(this, beforeChild); } // Remove all descendant layers from the hierarchy and add them to the new position. for (RenderObject* curr = renderer()->firstChild(); curr; curr = curr->nextSibling()) curr->moveLayers(m_parent, this); // Clear out all the clip rects. clearClipRectsIncludingDescendants(); } void RenderLayer::convertToPixelSnappedLayerCoords(const RenderLayer* ancestorLayer, IntPoint& roundedLocation, ColumnOffsetAdjustment adjustForColumns) const { LayoutPoint location = roundedLocation; convertToLayerCoords(ancestorLayer, location, adjustForColumns); roundedLocation = roundedIntPoint(location); } void RenderLayer::convertToPixelSnappedLayerCoords(const RenderLayer* ancestorLayer, IntRect& roundedRect, ColumnOffsetAdjustment adjustForColumns) const { LayoutRect rect = roundedRect; convertToLayerCoords(ancestorLayer, rect, adjustForColumns); roundedRect = pixelSnappedIntRect(rect); } // 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() : 0; if (fixedFlowThreadContainer && !fixedFlowThreadContainer->isOutOfFlowPositioned()) fixedFlowThreadContainer = 0; // 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 convertToLayerCoords() across a layer with a transform, // so we should always find the ancestor at or before we find the fixed position container. RenderLayer* fixedPositionContainerLayer = 0; bool foundAncestor = false; for (RenderLayer* currLayer = layer->parent(); currLayer; currLayer = currLayer->parent()) { if (currLayer == ancestorLayer) foundAncestor = true; if (isFixedPositionedContainer(currLayer)) { fixedPositionContainerLayer = currLayer; ASSERT_UNUSED(foundAncestor, foundAncestor); break; } } ASSERT(fixedPositionContainerLayer); // We should have hit the RenderView's layer at least. if (fixedPositionContainerLayer != ancestorLayer) { LayoutPoint fixedContainerCoords; layer->convertToLayerCoords(fixedPositionContainerLayer, fixedContainerCoords); LayoutPoint ancestorCoords; ancestorLayer->convertToLayerCoords(fixedPositionContainerLayer, ancestorCoords); location += (fixedContainerCoords - ancestorCoords); return ancestorLayer; } } RenderLayer* parentLayer; if (position == AbsolutePosition || position == FixedPosition) { // Do what enclosingPositionedAncestor() does, but check for ancestorLayer along the way. parentLayer = layer->parent(); bool foundAncestorFirst = false; while (parentLayer) { // RenderFlowThread is a positioned container, child of RenderView, positioned at (0,0). // This implies that, for out-of-flow positioned elements inside a RenderFlowThread, // we are bailing out before reaching root layer. if (isPositionedContainer(parentLayer)) 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 enclosingPositionedAncestor and subtract. RenderLayer* positionedAncestor = parentLayer->enclosingPositionedAncestor(); LayoutPoint thisCoords; layer->convertToLayerCoords(positionedAncestor, thisCoords); LayoutPoint ancestorCoords; ancestorLayer->convertToLayerCoords(positionedAncestor, ancestorCoords); location += (thisCoords - ancestorCoords); return ancestorLayer; } } else parentLayer = layer->parent(); if (!parentLayer) return 0; location += toSize(layer->location()); if (adjustForColumns == RenderLayer::AdjustForColumns) { if (RenderLayer* parentLayer = layer->parent()) { LayoutSize layerColumnOffset; parentLayer->renderer()->adjustForColumns(layerColumnOffset, location); location += layerColumnOffset; } } return parentLayer; } void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutPoint& location, ColumnOffsetAdjustment adjustForColumns) const { if (ancestorLayer == this) return; const RenderLayer* currLayer = this; while (currLayer && currLayer != ancestorLayer) currLayer = accumulateOffsetTowardsAncestor(currLayer, ancestorLayer, location, adjustForColumns); } void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, LayoutRect& rect, ColumnOffsetAdjustment adjustForColumns) const { LayoutPoint delta; convertToLayerCoords(ancestorLayer, delta, adjustForColumns); rect.move(-delta.x(), -delta.y()); } #if USE(ACCELERATED_COMPOSITING) bool RenderLayer::usesCompositedScrolling() const { return isComposited() && backing()->scrollingLayer(); } bool RenderLayer::needsCompositedScrolling() const { return m_needsCompositedScrolling; } void RenderLayer::updateNeedsCompositedScrolling() { bool oldNeedsCompositedScrolling = m_needsCompositedScrolling; FrameView* frameView = renderer()->view()->frameView(); if (!frameView || !frameView->containsScrollableArea(this)) m_needsCompositedScrolling = false; else { bool forceUseCompositedScrolling = acceleratedCompositingForOverflowScrollEnabled() && canBeStackingContainer() && !hasOutOfFlowPositionedDescendant(); #if ENABLE(ACCELERATED_OVERFLOW_SCROLLING) m_needsCompositedScrolling = forceUseCompositedScrolling || renderer()->style()->useTouchOverflowScrolling(); #else m_needsCompositedScrolling = forceUseCompositedScrolling; #endif // We gather a boolean value for use with Google UMA histograms to // quantify the actual effects of a set of patches attempting to // relax composited scrolling requirements, thereby increasing the // number of composited overflow divs. if (acceleratedCompositingForOverflowScrollEnabled()) HistogramSupport::histogramEnumeration("Renderer.NeedsCompositedScrolling", m_needsCompositedScrolling, 2); } if (oldNeedsCompositedScrolling != m_needsCompositedScrolling) { updateSelfPaintingLayer(); if (isStackingContainer()) dirtyZOrderLists(); else clearZOrderLists(); dirtyStackingContainerZOrderLists(); compositor()->setShouldReevaluateCompositingAfterLayout(); compositor()->setCompositingLayersNeedRebuild(); } } #endif 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) { Frame* frame = renderer()->frame(); if (!frame) return; IntPoint lastKnownMousePosition = 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); } void RenderLayer::scrollByRecursively(const IntSize& delta, ScrollOffsetClamping clamp) { if (delta.isZero()) return; bool restrictedByLineClamp = false; if (renderer()->parent()) restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone(); if (renderer()->hasOverflowClip() && !restrictedByLineClamp) { IntSize newScrollOffset = scrollOffset() + delta; scrollToOffset(newScrollOffset, clamp); // 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); Frame* frame = renderer()->frame(); if (frame) frame->eventHandler()->updateAutoscrollRenderer(); } } else if (renderer()->view()->frameView()) { // 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); // 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 } } IntSize RenderLayer::clampScrollOffset(const IntSize& scrollOffset) const { RenderBox* box = renderBox(); ASSERT(box); int maxX = scrollWidth() - box->pixelSnappedClientWidth(); int maxY = scrollHeight() - box->pixelSnappedClientHeight(); int x = max(min(scrollOffset.width(), maxX), 0); int y = max(min(scrollOffset.height(), maxY), 0); return IntSize(x, y); } void RenderLayer::scrollToOffset(const IntSize& scrollOffset, ScrollOffsetClamping clamp) { IntSize newScrollOffset = clamp == ScrollOffsetClamped ? clampScrollOffset(scrollOffset) : scrollOffset; if (newScrollOffset != this->scrollOffset()) scrollToOffsetWithoutAnimation(IntPoint(newScrollOffset)); } void RenderLayer::scrollTo(int x, int y) { RenderBox* box = renderBox(); if (!box) return; 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(); } // FIXME: Eventually, we will want to perform a blit. For now never // blit, since the check for blitting is going to be very // complicated (since it will involve testing whether our layer // is either occluded by another layer or clipped by an enclosing // layer or contains fixed backgrounds, etc.). IntSize newScrollOffset = IntSize(x - scrollOrigin().x(), y - scrollOrigin().y()); if (m_scrollOffset == newScrollOffset) return; m_scrollOffset = newScrollOffset; Frame* frame = renderer()->frame(); InspectorInstrumentation::willScrollLayer(frame); RenderView* view = renderer()->view(); // We should have a RenderView if we're trying to scroll. ASSERT(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. bool inLayout = view ? view->frameView()->isInLayout() : false; if (!inLayout) { // If we're in the middle of layout, we'll just update layers once layout has finished. updateLayerPositionsAfterOverflowScroll(); if (view) { // Update regions, scrolling may change the clip of a particular region. #if ENABLE(DASHBOARD_SUPPORT) || ENABLE(DRAGGABLE_REGION) view->frameView()->updateAnnotatedRegions(); #endif view->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(); } } RenderLayerModelObject* repaintContainer = renderer()->containerForRepaint(); if (frame) { // 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 USE(ACCELERATED_COMPOSITING) if (compositor()->inCompositingMode() && usesCompositedScrolling()) requiresRepaint = false; #endif // Just schedule a full repaint of our object. if (view && requiresRepaint) renderer()->repaintUsingContainer(repaintContainer, pixelSnappedIntRect(m_repaintRect)); // Schedule the scroll DOM event. if (renderer()->node()) renderer()->node()->document()->eventQueue()->enqueueOrDispatchScrollEvent(renderer()->node(), DocumentEventQueue::ScrollEventElementTarget); InspectorInstrumentation::didScrollLayer(frame); if (scrollsOverflow()) frame->loader()->client()->didChangeScrollOffset(); } 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(); } void RenderLayer::scrollRectToVisible(const LayoutRect& rect, const ScrollAlignment& alignX, const ScrollAlignment& alignY) { RenderLayer* parentLayer = 0; 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()->document()->view(); if (frameView) frameView->pauseScheduledEvents(); bool restrictedByLineClamp = false; if (renderer()->parent()) { parentLayer = renderer()->parent()->enclosingLayer(); restrictedByLineClamp = !renderer()->parent()->style()->lineClamp().isNone(); } if (renderer()->hasOverflowClip() && !restrictedByLineClamp) { // 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)), UseTransforms).boundingBox()); LayoutRect layerBounds(0, 0, box->clientWidth(), box->clientHeight()); LayoutRect r = getRectToExpose(layerBounds, layerBounds, localExposeRect, alignX, alignY); IntSize clampedScrollOffset = clampScrollOffset(scrollOffset() + toIntSize(roundedIntRect(r).location())); if (clampedScrollOffset != scrollOffset()) { IntSize oldScrollOffset = scrollOffset(); scrollToOffset(clampedScrollOffset); IntSize scrollOffsetDifference = scrollOffset() - oldScrollOffset; localExposeRect.move(-scrollOffsetDifference); newRect = LayoutRect(box->localToAbsoluteQuad(FloatQuad(FloatRect(localExposeRect)), UseTransforms).boundingBox()); } } else if (!parentLayer && renderer()->isBox() && renderBox()->canBeProgramaticallyScrolled()) { if (frameView) { Element* ownerElement = 0; if (renderer()->document()) ownerElement = renderer()->document()->ownerElement(); if (ownerElement && ownerElement->renderer()) { HTMLFrameElementBase* frameElementBase = 0; if (ownerElement->hasTagName(frameTag) || ownerElement->hasTagName(iframeTag)) frameElementBase = toHTMLFrameElementBase(ownerElement); if (frameElementAndViewPermitScroll(frameElementBase, frameView)) { LayoutRect viewRect = frameView->visibleContentRect(); LayoutRect exposeRect = getRectToExpose(viewRect, viewRect, rect, alignX, alignY); int xOffset = roundToInt(exposeRect.x()); int yOffset = roundToInt(exposeRect.y()); // Adjust offsets if they're outside of the allowable range. xOffset = max(0, min(frameView->contentsWidth(), xOffset)); yOffset = max(0, min(frameView->contentsHeight(), yOffset)); frameView->setScrollPosition(IntPoint(xOffset, yOffset)); if (frameView->safeToPropagateScrollToParent()) { parentLayer = ownerElement->renderer()->enclosingLayer(); // FIXME: This doesn't correctly convert the rect to // absolute coordinates in the parent. newRect.setX(rect.x() - frameView->scrollX() + frameView->x()); newRect.setY(rect.y() - frameView->scrollY() + frameView->y()); } else parentLayer = 0; } } else { LayoutRect viewRect = frameView->visibleContentRect(); LayoutRect visibleRectRelativeToDocument = viewRect; IntSize scrollOffsetRelativeToDocument = frameView->scrollOffsetRelativeToDocument(); visibleRectRelativeToDocument.setLocation(IntPoint(scrollOffsetRelativeToDocument.width(), scrollOffsetRelativeToDocument.height())); 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 (Frame* frame = frameView->frame()) { if (Page* page = frame->page()) page->chrome().scrollRectIntoView(pixelSnappedIntRect(rect)); } } } } if (parentLayer) parentLayer->scrollRectToVisible(newRect, alignX, alignY); if (frameView) frameView->resumeScheduledEvents(); } void RenderLayer::updateCompositingLayersAfterScroll() { #if USE(ACCELERATED_COMPOSITING) 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); } } #endif } 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) { Frame* frame = renderer()->frame(); if (!frame) return; FrameView* frameView = frame->view(); if (!frameView) return; IntPoint currentDocumentPosition = frameView->windowToContents(position); scrollRectToVisible(LayoutRect(currentDocumentPosition, LayoutSize(1, 1)), ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignToEdgeIfNeeded); } bool RenderLayer::canResize() const { if (!renderer()) return false; // We need a special case for