ScrollAnchor::ExamineResult ScrollAnchor::examine( const LayoutObject* candidate) const { if (candidate->isLayoutInline()) return ExamineResult(Continue); // Anonymous blocks are not in the DOM tree and it may be hard for // developers to reason about the anchor node. if (candidate->isAnonymous()) return ExamineResult(Continue); if (!candidate->isText() && !candidate->isBox()) return ExamineResult(Skip); if (!candidateMayMoveWithScroller(candidate, m_scroller)) return ExamineResult(Skip); if (candidate->style()->overflowAnchor() == AnchorNone) return ExamineResult(Skip); LayoutRect candidateRect = relativeBounds(candidate, m_scroller); LayoutRect visibleRect = scrollerLayoutBoxItem(m_scroller).overflowClipRect(LayoutPoint()); bool occupiesSpace = candidateRect.width() > 0 && candidateRect.height() > 0; if (occupiesSpace && visibleRect.intersects(candidateRect)) { return ExamineResult( visibleRect.contains(candidateRect) ? Return : Constrain, cornerToAnchor(m_scroller)); } else { return ExamineResult(Skip); } }
bool LayoutImage::updateImageLoadingPriorities() { if (!m_imageResource || !m_imageResource->cachedImage() || m_imageResource->cachedImage()->isLoaded()) return false; LayoutRect viewBounds = viewRect(); LayoutRect objectBounds = LayoutRect(absoluteContentBox()); // The object bounds might be empty right now, so intersects will fail since it doesn't deal // with empty rects. Use LayoutRect::contains in that case. bool isVisible; if (!objectBounds.isEmpty()) isVisible = viewBounds.intersects(objectBounds); else isVisible = viewBounds.contains(objectBounds); ResourceLoadPriorityOptimizer::VisibilityStatus status = isVisible ? ResourceLoadPriorityOptimizer::Visible : ResourceLoadPriorityOptimizer::NotVisible; LayoutRect screenArea; if (!objectBounds.isEmpty()) { screenArea = viewBounds; screenArea.intersect(objectBounds); } ResourceLoadPriorityOptimizer::resourceLoadPriorityOptimizer()->notifyImageResourceVisibility(m_imageResource->cachedImage(), status, screenArea); return true; }
bool RenderFlowThread::objectInFlowRegion(const RenderObject* object, const RenderRegion* region) const { ASSERT(object); ASSERT(region); if (!object->inRenderFlowThread()) return false; if (object->enclosingRenderFlowThread() != this) return false; if (!m_regionList.contains(const_cast<RenderRegion*>(region))) return false; RenderBox* enclosingBox = object->enclosingBox(); RenderRegion* enclosingBoxStartRegion = 0; RenderRegion* enclosingBoxEndRegion = 0; getRegionRangeForBox(enclosingBox, enclosingBoxStartRegion, enclosingBoxEndRegion); if (!regionInRange(region, enclosingBoxStartRegion, enclosingBoxEndRegion)) return false; if (object->isBox()) return true; LayoutRect objectABBRect = object->absoluteBoundingBoxRect(true); if (!objectABBRect.width()) objectABBRect.setWidth(1); if (!objectABBRect.height()) objectABBRect.setHeight(1); if (objectABBRect.intersects(region->absoluteBoundingBoxRect(true))) return true; if (region == lastRegion()) { // If the object does not intersect any of the enclosing box regions // then the object is in last region. for (RenderRegionList::const_iterator it = m_regionList.find(enclosingBoxStartRegion); it != m_regionList.end(); ++it) { const RenderRegion* currRegion = *it; if (!region->isValid()) continue; if (currRegion == region) break; if (objectABBRect.intersects(currRegion->absoluteBoundingBoxRect(true))) return false; } return true; } return false; }
void BlockPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) { Optional<SubtreeRecorder> subtreeRecorder; if (needsSubtreeRecorder(m_layoutBlock)) { subtreeRecorder.emplace(*paintInfo.context, m_layoutBlock, paintInfo.phase); if (subtreeRecorder->canUseCache()) return; } PaintInfo localPaintInfo(paintInfo); LayoutPoint adjustedPaintOffset = paintOffset + m_layoutBlock.location(); PaintPhase originalPhase = localPaintInfo.phase; // Check if we need to do anything at all. LayoutRect overflowBox = overflowRectForPaintRejection(); m_layoutBlock.flipForWritingMode(overflowBox); overflowBox.moveBy(adjustedPaintOffset); if (!overflowBox.intersects(LayoutRect(localPaintInfo.rect))) return; // There are some cases where not all clipped visual overflow is accounted for. // FIXME: reduce the number of such cases. ContentsClipBehavior contentsClipBehavior = ForceContentsClip; if (m_layoutBlock.hasOverflowClip() && !m_layoutBlock.hasControlClip() && !(m_layoutBlock.shouldPaintSelectionGaps() && originalPhase == PaintPhaseForeground) && !hasCaret()) contentsClipBehavior = SkipContentsClipIfPossible; if (localPaintInfo.phase == PaintPhaseOutline) { localPaintInfo.phase = PaintPhaseChildOutlines; } else if (localPaintInfo.phase == PaintPhaseChildBlockBackground) { localPaintInfo.phase = PaintPhaseBlockBackground; m_layoutBlock.paintObject(localPaintInfo, adjustedPaintOffset); localPaintInfo.phase = PaintPhaseChildBlockBackgrounds; } { BoxClipper boxClipper(m_layoutBlock, localPaintInfo, adjustedPaintOffset, contentsClipBehavior); m_layoutBlock.paintObject(localPaintInfo, adjustedPaintOffset); } if (originalPhase == PaintPhaseOutline) { localPaintInfo.phase = PaintPhaseSelfOutline; m_layoutBlock.paintObject(localPaintInfo, adjustedPaintOffset); localPaintInfo.phase = originalPhase; } else if (originalPhase == PaintPhaseChildBlockBackground) { localPaintInfo.phase = originalPhase; } // Our scrollbar widgets paint exactly when we tell them to, so that they work properly with // z-index. We paint after we painted the background/border, so that the scrollbars will // sit above the background/border. paintOverflowControlsIfNeeded(localPaintInfo, adjustedPaintOffset); }
bool BlockPainter::intersectsPaintRect(const PaintInfo& paintInfo, const LayoutPoint& paintOffset) const { LayoutRect overflowRect = m_layoutBlock.visualOverflowRect(); if (m_layoutBlock.hasOverflowModel() && m_layoutBlock.usesCompositedScrolling()) { overflowRect.unite(m_layoutBlock.layoutOverflowRect()); overflowRect.move(-m_layoutBlock.scrolledContentOffset()); } m_layoutBlock.flipForWritingMode(overflowRect); overflowRect.moveBy(paintOffset + m_layoutBlock.location()); return (overflowRect.intersects(LayoutRect(paintInfo.rect))); }
void Page::addRelevantUnpaintedObject(RenderObject* object, const LayoutRect& objectPaintRect) { if (!isCountingRelevantRepaintedObjects()) return; // The objects are only relevant if they are being painted within the relevantViewRect(). if (RenderView* view = object->view()) { if (!objectPaintRect.intersects(pixelSnappedIntRect(relevantViewRect(view)))) return; } m_relevantUnpaintedRenderObjects.add(object); m_relevantUnpaintedRegion.unite(pixelSnappedIntRect(objectPaintRect)); }
static void deflateIfOverlapped(LayoutRect& a, LayoutRect& b) { if (!a.intersects(b) || a.contains(b) || b.contains(a)) return; LayoutUnit deflateFactor = -fudgeFactor(); // Avoid negative width or height values. if ((a.width() + 2 * deflateFactor > 0) && (a.height() + 2 * deflateFactor > 0)) a.inflate(deflateFactor); if ((b.width() + 2 * deflateFactor > 0) && (b.height() + 2 * deflateFactor > 0)) b.inflate(deflateFactor); }
bool canBeScrolledIntoView(FocusType type, const FocusCandidate& candidate) { ASSERT(candidate.visibleNode && candidate.isOffscreen); LayoutRect candidateRect = candidate.rect; for (Node* parentNode = candidate.visibleNode->parentNode(); parentNode; parentNode = parentNode->parentNode()) { LayoutRect parentRect = nodeRectInAbsoluteCoordinates(parentNode); if (!candidateRect.intersects(parentRect)) { if (((type == FocusTypeLeft || type == FocusTypeRight) && parentNode->renderer()->style()->overflowX() == OHIDDEN) || ((type == FocusTypeUp || type == FocusTypeDown) && parentNode->renderer()->style()->overflowY() == OHIDDEN)) return false; } if (parentNode == candidate.enclosingScrollableBox) return canScrollInDirection(parentNode, type); } return true; }
// Checks if |node| is offscreen the visible area (viewport) of its container // document. In case it is, one can scroll in direction or take any different // desired action later on. bool hasOffscreenRect(Node* node, FocusType type) { // Get the FrameView in which |node| is (which means the current viewport if |node| // is not in an inner document), so we can check if its content rect is visible // before we actually move the focus to it. FrameView* frameView = node->document().view(); if (!frameView) return true; ASSERT(!frameView->needsLayout()); LayoutRect containerViewportRect = frameView->visibleContentRect(); // We want to select a node if it is currently off screen, but will be // exposed after we scroll. Adjust the viewport to post-scrolling position. // If the container has overflow:hidden, we cannot scroll, so we do not pass direction // and we do not adjust for scrolling. switch (type) { case FocusTypeLeft: containerViewportRect.setX(containerViewportRect.x() - ScrollableArea::pixelsPerLineStep()); containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); break; case FocusTypeRight: containerViewportRect.setWidth(containerViewportRect.width() + ScrollableArea::pixelsPerLineStep()); break; case FocusTypeUp: containerViewportRect.setY(containerViewportRect.y() - ScrollableArea::pixelsPerLineStep()); containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); break; case FocusTypeDown: containerViewportRect.setHeight(containerViewportRect.height() + ScrollableArea::pixelsPerLineStep()); break; default: break; } RenderObject* render = node->renderer(); if (!render) return true; LayoutRect rect(render->absoluteClippedOverflowRect()); if (rect.isEmpty()) return true; return !containerViewportRect.intersects(rect); }
// Hit Testing bool RenderRegion::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, HitTestAction action) { if (!isValid()) return false; LayoutPoint adjustedLocation = accumulatedOffset + location(); // Check our bounds next. For this purpose always assume that we can only be hit in the // foreground phase (which is true for replaced elements like images). LayoutRect boundsRect = borderBoxRectInRegion(result.region()); boundsRect.moveBy(adjustedLocation); if (visibleToHitTesting() && action == HitTestForeground && boundsRect.intersects(result.rectForPoint(pointInContainer))) { // Check the contents of the RenderFlowThread. if (m_flowThread && m_flowThread->hitTestRegion(this, request, result, pointInContainer, LayoutPoint(adjustedLocation.x() + borderLeft() + paddingLeft(), adjustedLocation.y() + borderTop() + paddingTop()))) return true; updateHitTestResult(result, pointInContainer - toLayoutSize(adjustedLocation)); if (!result.addNodeToRectBasedTestResult(node(), pointInContainer, boundsRect)) return true; } return false; }
void Page::addRelevantRepaintedObject(RenderObject* object, const LayoutRect& objectPaintRect) { if (!isCountingRelevantRepaintedObjects()) return; // Objects inside sub-frames are not considered to be relevant. if (object->document()->frame() != mainFrame()) return; RenderView* view = object->view(); if (!view) return; LayoutRect relevantRect = relevantViewRect(view); // The objects are only relevant if they are being painted within the viewRect(). if (!objectPaintRect.intersects(pixelSnappedIntRect(relevantRect))) return; IntRect snappedPaintRect = pixelSnappedIntRect(objectPaintRect); // If this object was previously counted as an unpainted object, remove it from that HashSet // and corresponding Region. FIXME: This doesn't do the right thing if the objects overlap. HashSet<RenderObject*>::iterator it = m_relevantUnpaintedRenderObjects.find(object); if (it != m_relevantUnpaintedRenderObjects.end()) { m_relevantUnpaintedRenderObjects.remove(it); m_relevantUnpaintedRegion.subtract(snappedPaintRect); } // Split the relevantRect into a top half and a bottom half. Making sure we have coverage in // both halves helps to prevent cases where we have a fully loaded menu bar or masthead with // no content beneath that. LayoutRect topRelevantRect = relevantRect; topRelevantRect.contract(LayoutSize(0, relevantRect.height() / 2)); LayoutRect bottomRelevantRect = topRelevantRect; bottomRelevantRect.setY(relevantRect.height() / 2); // If the rect straddles both Regions, split it appropriately. if (topRelevantRect.intersects(snappedPaintRect) && bottomRelevantRect.intersects(snappedPaintRect)) { IntRect topIntersection = snappedPaintRect; topIntersection.intersect(pixelSnappedIntRect(topRelevantRect)); m_topRelevantPaintedRegion.unite(topIntersection); IntRect bottomIntersection = snappedPaintRect; bottomIntersection.intersect(pixelSnappedIntRect(bottomRelevantRect)); m_bottomRelevantPaintedRegion.unite(bottomIntersection); } else if (topRelevantRect.intersects(snappedPaintRect)) m_topRelevantPaintedRegion.unite(snappedPaintRect); else m_bottomRelevantPaintedRegion.unite(snappedPaintRect); float topPaintedArea = m_topRelevantPaintedRegion.totalArea(); float bottomPaintedArea = m_bottomRelevantPaintedRegion.totalArea(); float viewArea = relevantRect.width() * relevantRect.height(); float ratioThatIsPaintedOnTop = topPaintedArea / viewArea; float ratioThatIsPaintedOnBottom = bottomPaintedArea / viewArea; float ratioOfViewThatIsUnpainted = m_relevantUnpaintedRegion.totalArea() / viewArea; if (ratioThatIsPaintedOnTop > (gMinimumPaintedAreaRatio / 2) && ratioThatIsPaintedOnBottom > (gMinimumPaintedAreaRatio / 2) && ratioOfViewThatIsUnpainted < gMaximumUnpaintedAreaRatio) { m_isCountingRelevantRepaintedObjects = false; resetRelevantPaintedObjectCounter(); if (Frame* frame = mainFrame()) frame->loader()->didLayout(DidHitRelevantRepaintedObjectsAreaThreshold); } }
bool CompositingReasonFinder::requiresCompositingForPosition(RenderObject* renderer, const RenderLayer* layer, RenderLayer::ViewportConstrainedNotCompositedReason* viewportConstrainedNotCompositedReason, bool* needToRecomputeCompositingRequirements) const { // position:fixed elements that create their own stacking context (e.g. have an explicit z-index, // opacity, transform) can get their own composited layer. A stacking context is required otherwise // z-index and clipping will be broken. if (!renderer->isPositioned()) return false; EPosition position = renderer->style()->position(); bool isFixed = renderer->isOutOfFlowPositioned() && position == FixedPosition; // FIXME: The isStackingContainer check here is redundant. Fixed position elements are always stacking contexts. if (isFixed && !layer->stackingNode()->isStackingContainer()) return false; bool isSticky = renderer->isInFlowPositioned() && position == StickyPosition; if (!isFixed && !isSticky) return false; // FIXME: acceleratedCompositingForFixedPositionEnabled should probably be renamed acceleratedCompositingForViewportConstrainedPositionEnabled(). if (Settings* settings = m_renderView.document().settings()) { if (!settings->acceleratedCompositingForFixedPositionEnabled()) return false; } if (isSticky) return isViewportConstrainedFixedOrStickyLayer(layer); RenderObject* container = renderer->container(); // If the renderer is not hooked up yet then we have to wait until it is. if (!container) { *needToRecomputeCompositingRequirements = true; return false; } // Don't promote fixed position elements that are descendants of a non-view container, e.g. transformed elements. // They will stay fixed wrt the container rather than the enclosing frame. if (container != &m_renderView) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForNonViewContainer; return false; } // If the fixed-position element does not have any scrollable ancestor between it and // its container, then we do not need to spend compositor resources for it. Start by // assuming we can opt-out (i.e. no scrollable ancestor), and refine the answer below. bool hasScrollableAncestor = false; // The FrameView has the scrollbars associated with the top level viewport, so we have to // check the FrameView in addition to the hierarchy of ancestors. FrameView* frameView = m_renderView.frameView(); if (frameView && frameView->isScrollable()) hasScrollableAncestor = true; RenderLayer* ancestor = layer->parent(); while (ancestor && !hasScrollableAncestor) { if (frameView->containsScrollableArea(ancestor->scrollableArea())) hasScrollableAncestor = true; if (ancestor->renderer() == &m_renderView) break; ancestor = ancestor->parent(); } if (!hasScrollableAncestor) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForUnscrollableAncestors; return false; } // Subsequent tests depend on layout. If we can't tell now, just keep things the way they are until layout is done. if (m_renderView.document().lifecycle().state() < DocumentLifecycle::LayoutClean) { *needToRecomputeCompositingRequirements = true; return layer->hasCompositedLayerMapping(); } bool paintsContent = layer->isVisuallyNonEmpty() || layer->hasVisibleDescendant(); if (!paintsContent) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForNoVisibleContent; return false; } // Fixed position elements that are invisible in the current view don't get their own layer. if (FrameView* frameView = m_renderView.frameView()) { LayoutRect viewBounds = frameView->viewportConstrainedVisibleContentRect(); LayoutRect layerBounds = layer->calculateLayerBounds(layer->compositor()->rootRenderLayer(), 0, RenderLayer::DefaultCalculateLayerBoundsFlags | RenderLayer::ExcludeHiddenDescendants | RenderLayer::DontConstrainForMask | RenderLayer::IncludeCompositedDescendants | RenderLayer::PretendLayerHasOwnBacking); if (!viewBounds.intersects(enclosingIntRect(layerBounds))) { if (viewportConstrainedNotCompositedReason) { *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForBoundsOutOfView; *needToRecomputeCompositingRequirements = true; } return false; } } return true; }
bool CompositingReasonFinder::requiresCompositingForPositionFixed(RenderObject* renderer, const RenderLayer* layer, RenderLayer::ViewportConstrainedNotCompositedReason* viewportConstrainedNotCompositedReason, bool* needToRecomputeCompositingRequirements) const { if (!(m_compositingTriggers & ViewportConstrainedPositionedTrigger)) return false; if (renderer->style()->position() != FixedPosition) return false; RenderObject* container = renderer->container(); // If the renderer is not hooked up yet then we have to wait until it is. if (!container) { ASSERT(m_renderView.document().lifecycle().state() < DocumentLifecycle::InCompositingUpdate); // FIXME: Remove this and ASSERT(container) once we get rid of the incremental // allocateOrClearCompositedLayerMapping compositing update. This happens when // adding the renderer to the tree because we setStyle before addChild in // createRendererForElementIfNeeded. *needToRecomputeCompositingRequirements = true; return false; } // Don't promote fixed position elements that are descendants of a non-view container, e.g. transformed elements. // They will stay fixed wrt the container rather than the enclosing frame. if (container != &m_renderView) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForNonViewContainer; return false; } // If the fixed-position element does not have any scrollable ancestor between it and // its container, then we do not need to spend compositor resources for it. Start by // assuming we can opt-out (i.e. no scrollable ancestor), and refine the answer below. bool hasScrollableAncestor = false; // The FrameView has the scrollbars associated with the top level viewport, so we have to // check the FrameView in addition to the hierarchy of ancestors. FrameView* frameView = m_renderView.frameView(); if (frameView && frameView->isScrollable()) hasScrollableAncestor = true; RenderLayer* ancestor = layer->parent(); while (ancestor && !hasScrollableAncestor) { if (ancestor->scrollsOverflow()) hasScrollableAncestor = true; if (ancestor->renderer() == &m_renderView) break; ancestor = ancestor->parent(); } if (!hasScrollableAncestor) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForUnscrollableAncestors; return false; } // Subsequent tests depend on layout. If we can't tell now, just keep things the way they are until layout is done. // FIXME: Get rid of this codepath once we get rid of the incremental compositing update in RenderLayer::styleChanged. if (m_renderView.document().lifecycle().state() < DocumentLifecycle::LayoutClean) { *needToRecomputeCompositingRequirements = true; return layer->hasCompositedLayerMapping(); } bool paintsContent = layer->isVisuallyNonEmpty() || layer->hasVisibleDescendant(); if (!paintsContent) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForNoVisibleContent; return false; } // Fixed position elements that are invisible in the current view don't get their own layer. if (FrameView* frameView = m_renderView.frameView()) { ASSERT(m_renderView.document().lifecycle().state() == DocumentLifecycle::InCompositingUpdate); LayoutRect viewBounds = frameView->viewportConstrainedVisibleContentRect(); LayoutRect layerBounds = layer->boundingBoxForCompositing(layer->compositor()->rootRenderLayer(), RenderLayer::ApplyBoundsChickenEggHacks); if (!viewBounds.intersects(enclosingIntRect(layerBounds))) { if (viewportConstrainedNotCompositedReason) *viewportConstrainedNotCompositedReason = RenderLayer::NotCompositedForBoundsOutOfView; return false; } } return true; }