diff options
Diffstat (limited to 'Source/WebCore/rendering/RenderView.cpp')
-rw-r--r-- | Source/WebCore/rendering/RenderView.cpp | 1098 |
1 files changed, 651 insertions, 447 deletions
diff --git a/Source/WebCore/rendering/RenderView.cpp b/Source/WebCore/rendering/RenderView.cpp index 2968bf21d..e9f1f700a 100644 --- a/Source/WebCore/rendering/RenderView.cpp +++ b/Source/WebCore/rendering/RenderView.cpp @@ -21,7 +21,6 @@ #include "config.h" #include "RenderView.h" -#include "ColumnInfo.h" #include "Document.h" #include "Element.h" #include "FloatQuad.h" @@ -31,46 +30,101 @@ #include "FrameSelection.h" #include "FrameView.h" #include "GraphicsContext.h" +#include "HTMLBodyElement.h" #include "HTMLFrameOwnerElement.h" +#include "HTMLHtmlElement.h" #include "HTMLIFrameElement.h" #include "HitTestResult.h" #include "ImageQualityController.h" +#include "NodeTraversal.h" #include "Page.h" #include "RenderGeometryMap.h" #include "RenderIterator.h" #include "RenderLayer.h" #include "RenderLayerBacking.h" +#include "RenderLayerCompositor.h" +#include "RenderMultiColumnFlowThread.h" +#include "RenderMultiColumnSet.h" +#include "RenderMultiColumnSpannerPlaceholder.h" #include "RenderNamedFlowThread.h" #include "RenderSelectionInfo.h" #include "RenderWidget.h" +#include "ScrollbarTheme.h" +#include "Settings.h" #include "StyleInheritedData.h" #include "TransformState.h" +#include <wtf/SetForScope.h> #include <wtf/StackStats.h> -#if USE(ACCELERATED_COMPOSITING) -#include "RenderLayerCompositor.h" -#endif - namespace WebCore { -RenderView::RenderView(Document& document, PassRef<RenderStyle> style) - : RenderBlockFlow(document, std::move(style)) +struct FrameFlatteningLayoutDisallower { + FrameFlatteningLayoutDisallower(FrameView& frameView) + : m_frameView(frameView) + , m_disallowLayout(frameView.frame().settings().frameFlatteningEnabled()) + { + if (m_disallowLayout) + m_frameView.startDisallowingLayout(); + } + + ~FrameFlatteningLayoutDisallower() + { + if (m_disallowLayout) + m_frameView.endDisallowingLayout(); + } + +private: + FrameView& m_frameView; + bool m_disallowLayout { false }; +}; + +struct SelectionIterator { + SelectionIterator(RenderObject* start) + : m_current(start) + { + checkForSpanner(); + } + + RenderObject* current() const + { + return m_current; + } + + RenderObject* next() + { + RenderObject* currentSpan = m_spannerStack.isEmpty() ? nullptr : m_spannerStack.last()->spanner(); + m_current = m_current->nextInPreOrder(currentSpan); + checkForSpanner(); + if (!m_current && currentSpan) { + RenderObject* placeholder = m_spannerStack.last(); + m_spannerStack.removeLast(); + m_current = placeholder->nextInPreOrder(); + checkForSpanner(); + } + return m_current; + } + +private: + void checkForSpanner() + { + if (!is<RenderMultiColumnSpannerPlaceholder>(m_current)) + return; + auto& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*m_current); + m_spannerStack.append(&placeholder); + m_current = placeholder.spanner(); + } + + RenderObject* m_current { nullptr }; + Vector<RenderMultiColumnSpannerPlaceholder*> m_spannerStack; +}; + +RenderView::RenderView(Document& document, RenderStyle&& style) + : RenderBlockFlow(document, WTFMove(style)) , m_frameView(*document.view()) - , m_selectionStart(0) - , m_selectionEnd(0) - , m_selectionStartPos(-1) - , m_selectionEndPos(-1) - , m_rendererCount(0) - , m_maximalOutlineSize(0) - , m_pageLogicalHeight(0) - , m_pageLogicalHeightChanged(false) - , m_layoutState(nullptr) - , m_layoutStateDisableCount(0) - , m_renderQuoteHead(0) - , m_renderCounterCount(0) - , m_selectionWasCaret(false) -#if ENABLE(CSS_FILTERS) - , m_hasSoftwareFilters(false) + , m_weakFactory(this) + , m_lazyRepaintTimer(*this, &RenderView::lazyRepaintTimerFired) +#if ENABLE(SERVICE_CONTROLS) + , m_selectionRectGatherer(*this) #endif { setIsRenderView(); @@ -93,6 +147,35 @@ RenderView::~RenderView() { } +void RenderView::scheduleLazyRepaint(RenderBox& renderer) +{ + if (renderer.renderBoxNeedsLazyRepaint()) + return; + renderer.setRenderBoxNeedsLazyRepaint(true); + m_renderersNeedingLazyRepaint.add(&renderer); + if (!m_lazyRepaintTimer.isActive()) + m_lazyRepaintTimer.startOneShot(0); +} + +void RenderView::unscheduleLazyRepaint(RenderBox& renderer) +{ + if (!renderer.renderBoxNeedsLazyRepaint()) + return; + renderer.setRenderBoxNeedsLazyRepaint(false); + m_renderersNeedingLazyRepaint.remove(&renderer); + if (m_renderersNeedingLazyRepaint.isEmpty()) + m_lazyRepaintTimer.stop(); +} + +void RenderView::lazyRepaintTimerFired() +{ + for (auto& renderer : m_renderersNeedingLazyRepaint) { + renderer->repaint(); + renderer->setRenderBoxNeedsLazyRepaint(false); + } + m_renderersNeedingLazyRepaint.clear(); +} + bool RenderView::hitTest(const HitTestRequest& request, HitTestResult& result) { return hitTest(request, result.hitTestLocation(), result); @@ -100,26 +183,36 @@ bool RenderView::hitTest(const HitTestRequest& request, HitTestResult& result) bool RenderView::hitTest(const HitTestRequest& request, const HitTestLocation& location, HitTestResult& result) { - if (layer()->hitTest(request, location, result)) - return true; - - // FIXME: Consider if this test should be done unconditionally. - if (request.allowsFrameScrollbars()) { - // ScrollView scrollbars are not the same as RenderLayer scrollbars tested by RenderLayer::hitTestOverflowControls, - // so we need to test ScrollView scrollbars separately here. - Scrollbar* frameScrollbar = frameView().scrollbarAtPoint(location.roundedPoint()); - if (frameScrollbar) { - result.setScrollbar(frameScrollbar); - return true; + document().updateLayout(); + +#if !ASSERT_DISABLED + SetForScope<bool> hitTestRestorer { m_inHitTesting, true }; +#endif + + FrameFlatteningLayoutDisallower disallower(frameView()); + + bool resultLayer = layer()->hitTest(request, location, result); + + // ScrollView scrollbars are not the same as RenderLayer scrollbars tested by RenderLayer::hitTestOverflowControls, + // so we need to test ScrollView scrollbars separately here. In case of using overlay scrollbars, the layer hit test + // will always work so we need to check the ScrollView scrollbars in that case too. + if (!resultLayer || ScrollbarTheme::theme().usesOverlayScrollbars()) { + // FIXME: Consider if this test should be done unconditionally. + if (request.allowsFrameScrollbars()) { + IntPoint windowPoint = frameView().contentsToWindow(location.roundedPoint()); + if (Scrollbar* frameScrollbar = frameView().scrollbarAtPoint(windowPoint)) { + result.setScrollbar(frameScrollbar); + return true; + } } } - return false; + return resultLayer; } -void RenderView::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit, LogicalExtentComputedValues& computedValues) const +RenderBox::LogicalExtentComputedValues RenderView::computeLogicalHeight(LayoutUnit logicalHeight, LayoutUnit) const { - computedValues.m_extent = !shouldUsePrintingLayout() ? LayoutUnit(viewLogicalHeight()) : logicalHeight; + return { !shouldUsePrintingLayout() ? LayoutUnit(viewLogicalHeight()) : logicalHeight, LayoutUnit(), ComputedMarginValues() }; } void RenderView::updateLogicalWidth() @@ -130,9 +223,11 @@ void RenderView::updateLogicalWidth() LayoutUnit RenderView::availableLogicalHeight(AvailableLogicalHeightType) const { - // If we have columns, then the available logical height is reduced to the column height. - if (hasColumns()) - return columnInfo()->columnHeight(); + // Make sure block progression pagination for percentages uses the column extent and + // not the view's extent. See https://bugs.webkit.org/show_bug.cgi?id=135204. + if (multiColumnFlowThread() && multiColumnFlowThread()->firstMultiColumnSet()) + return multiColumnFlowThread()->firstMultiColumnSet()->computedColumnHeight(); + #if PLATFORM(IOS) // Workaround for <rdar://problem/7166808>. if (document().isPluginDocument() && frameView().useFixedLayout()) @@ -168,67 +263,15 @@ void RenderView::checkLayoutState(const LayoutState& state) } #endif -static RenderBox* enclosingSeamlessRenderer(Document& document) -{ - Element* ownerElement = document.seamlessParentIFrame(); - if (!ownerElement) - return 0; - return ownerElement->renderBox(); -} - -void RenderView::addChild(RenderObject* newChild, RenderObject* beforeChild) +void RenderView::initializeLayoutState(LayoutState& state) { - // Seamless iframes are considered part of an enclosing render flow thread from the parent document. This is necessary for them to look - // up regions in the parent document during layout. - if (newChild && !newChild->isRenderFlowThread()) { - RenderBox* seamlessBox = enclosingSeamlessRenderer(document()); - if (seamlessBox && seamlessBox->flowThreadContainingBlock()) - newChild->setFlowThreadState(seamlessBox->flowThreadState()); - } - RenderBlockFlow::addChild(newChild, beforeChild); -} - -bool RenderView::initializeLayoutState(LayoutState& state) -{ - bool isSeamlessAncestorInFlowThread = false; - // FIXME: May be better to push a clip and avoid issuing offscreen repaints. state.m_clipped = false; - - // Check the writing mode of the seamless ancestor. It has to match our document's writing mode, or we won't inherit any - // pagination information. - RenderBox* seamlessAncestor = enclosingSeamlessRenderer(document()); - LayoutState* seamlessLayoutState = seamlessAncestor ? seamlessAncestor->view().layoutState() : 0; - bool shouldInheritPagination = seamlessLayoutState && !m_pageLogicalHeight && seamlessAncestor->style().writingMode() == style().writingMode(); - - state.m_pageLogicalHeight = shouldInheritPagination ? seamlessLayoutState->m_pageLogicalHeight : m_pageLogicalHeight; - state.m_pageLogicalHeightChanged = shouldInheritPagination ? seamlessLayoutState->m_pageLogicalHeightChanged : m_pageLogicalHeightChanged; - state.m_isPaginated = state.m_pageLogicalHeight; - if (state.m_isPaginated && shouldInheritPagination) { - // Set up the correct pagination offset. We can use a negative offset in order to push the top of the RenderView into its correct place - // on a page. We can take the iframe's offset from the logical top of the first page and make the negative into the pagination offset within the child - // view. - bool isFlipped = seamlessAncestor->style().isFlippedBlocksWritingMode(); - LayoutSize layoutOffset = seamlessLayoutState->layoutOffset(); - LayoutSize iFrameOffset(layoutOffset.width() + seamlessAncestor->x() + (!isFlipped ? seamlessAncestor->borderLeft() + seamlessAncestor->paddingLeft() : - seamlessAncestor->borderRight() + seamlessAncestor->paddingRight()), - layoutOffset.height() + seamlessAncestor->y() + (!isFlipped ? seamlessAncestor->borderTop() + seamlessAncestor->paddingTop() : - seamlessAncestor->borderBottom() + seamlessAncestor->paddingBottom())); - - LayoutSize offsetDelta = seamlessLayoutState->m_pageOffset - iFrameOffset; - state.m_pageOffset = offsetDelta; - - // Set the current render flow thread to point to our ancestor. This will allow the seamless document to locate the correct - // regions when doing a layout. - if (seamlessAncestor->flowThreadContainingBlock()) { - flowThreadController().setCurrentRenderFlowThread(seamlessAncestor->view().flowThreadController().currentRenderFlowThread()); - isSeamlessAncestorInFlowThread = true; - } - } - // FIXME: We need to make line grids and exclusions work with seamless iframes as well here. Basically all layout state information needs - // to propagate here and not just pagination information. - return isSeamlessAncestorInFlowThread; + state.m_pageLogicalHeight = m_pageLogicalHeight; + state.m_pageLogicalHeightChanged = m_pageLogicalHeightChanged; + ASSERT(state.m_pageLogicalHeight >= 0); + state.m_isPaginated = state.m_pageLogicalHeight > 0; } // The algorithm below assumes this is a full layout. In case there are previously computed values for regions, supplemental steps are taken @@ -308,16 +351,10 @@ void RenderView::layout() for (auto& box : childrenOfType<RenderBox>(*this)) { if (box.hasRelativeLogicalHeight() - || box.hasViewportPercentageLogicalHeight() - || box.style().logicalHeight().isPercent() - || box.style().logicalMinHeight().isPercent() - || box.style().logicalMaxHeight().isPercent() - || box.style().logicalHeight().isViewportPercentage() - || box.style().logicalMinHeight().isViewportPercentage() - || box.style().logicalMaxHeight().isViewportPercentage() -#if ENABLE(SVG) + || box.style().logicalHeight().isPercentOrCalculated() + || box.style().logicalMinHeight().isPercentOrCalculated() + || box.style().logicalMaxHeight().isPercentOrCalculated() || box.isSVGRoot() -#endif ) box.setChildNeedsLayout(MarkOnlyThis); } @@ -328,7 +365,7 @@ void RenderView::layout() return; m_layoutState = std::make_unique<LayoutState>(); - bool isSeamlessAncestorInFlowThread = initializeLayoutState(*m_layoutState); + initializeLayoutState(*m_layoutState); m_pageLogicalHeightChanged = false; @@ -344,9 +381,6 @@ void RenderView::layout() #endif m_layoutState = nullptr; clearNeedsLayout(); - - if (isSeamlessAncestorInFlowThread) - flowThreadController().setCurrentRenderFlowThread(0); } LayoutUnit RenderView::pageOrViewLogicalHeight() const @@ -354,7 +388,7 @@ LayoutUnit RenderView::pageOrViewLogicalHeight() const if (document().printing()) return pageLogicalHeight(); - if (hasColumns() && !style().hasInlineColumnAxis()) { + if (multiColumnFlowThread() && !style().hasInlineColumnAxis()) { if (int pageLength = frameView().pagination().pageLength) return pageLength; } @@ -373,6 +407,9 @@ LayoutUnit RenderView::clientLogicalWidthForFixedPosition() const return isHorizontalWritingMode() ? frameView().customFixedPositionLayoutRect().width() : frameView().customFixedPositionLayoutRect().height(); #endif + if (settings().visualViewportEnabled()) + return isHorizontalWritingMode() ? frameView().layoutViewportRect().width() : frameView().layoutViewportRect().height(); + return clientLogicalWidth(); } @@ -387,73 +424,57 @@ LayoutUnit RenderView::clientLogicalHeightForFixedPosition() const return isHorizontalWritingMode() ? frameView().customFixedPositionLayoutRect().height() : frameView().customFixedPositionLayoutRect().width(); #endif - return clientLogicalHeight(); -} + if (settings().visualViewportEnabled()) + return isHorizontalWritingMode() ? frameView().layoutViewportRect().height() : frameView().layoutViewportRect().width(); -#if PLATFORM(IOS) -static inline LayoutSize fixedPositionOffset(const FrameView& frameView) -{ - return frameView.useCustomFixedPositionLayoutRect() ? (frameView.customFixedPositionLayoutRect().location() - LayoutPoint()) : frameView.scrollOffset(); + return clientLogicalHeight(); } -#endif void RenderView::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags mode, bool* wasFixed) const { - // If a container was specified, and was not 0 or the RenderView, + // If a container was specified, and was not nullptr or the RenderView, // then we should have found it by now. ASSERT_ARG(repaintContainer, !repaintContainer || repaintContainer == this); ASSERT_UNUSED(wasFixed, !wasFixed || *wasFixed == (mode & IsFixed)); - if (!repaintContainer && mode & UseTransforms && shouldUseTransformFromContainer(0)) { + if (mode & IsFixed) + transformState.move(toLayoutSize(frameView().scrollPositionRespectingCustomFixedPosition())); + + if (!repaintContainer && mode & UseTransforms && shouldUseTransformFromContainer(nullptr)) { TransformationMatrix t; - getTransformFromContainer(0, LayoutSize(), t); + getTransformFromContainer(nullptr, LayoutSize(), t); transformState.applyTransform(t); } - - if (mode & IsFixed) -#if PLATFORM(IOS) - transformState.move(fixedPositionOffset(m_frameView)); -#else - transformState.move(frameView().scrollOffsetForFixedPosition()); -#endif } const RenderObject* RenderView::pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap& geometryMap) const { - // If a container was specified, and was not 0 or the RenderView, + // If a container was specified, and was not nullptr or the RenderView, // then we should have found it by now. ASSERT_ARG(ancestorToStopAt, !ancestorToStopAt || ancestorToStopAt == this); -#if PLATFORM(IOS) - LayoutSize scrollOffset = fixedPositionOffset(frameView()); -#else - LayoutSize scrollOffset = frameView().scrollOffsetForFixedPosition(); -#endif + LayoutPoint scrollPosition = frameView().scrollPositionRespectingCustomFixedPosition(); - if (!ancestorToStopAt && shouldUseTransformFromContainer(0)) { + if (!ancestorToStopAt && shouldUseTransformFromContainer(nullptr)) { TransformationMatrix t; - getTransformFromContainer(0, LayoutSize(), t); - geometryMap.pushView(this, scrollOffset, &t); + getTransformFromContainer(nullptr, LayoutSize(), t); + geometryMap.pushView(this, toLayoutSize(scrollPosition), &t); } else - geometryMap.pushView(this, scrollOffset); + geometryMap.pushView(this, toLayoutSize(scrollPosition)); - return 0; + return nullptr; } void RenderView::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const { - if (mode & IsFixed) -#if PLATFORM(IOS) - transformState.move(fixedPositionOffset(frameView())); -#else - transformState.move(frameView().scrollOffsetForFixedPosition()); -#endif - - if (mode & UseTransforms && shouldUseTransformFromContainer(0)) { + if (mode & UseTransforms && shouldUseTransformFromContainer(nullptr)) { TransformationMatrix t; - getTransformFromContainer(0, LayoutSize(), t); + getTransformFromContainer(nullptr, LayoutSize(), t); transformState.applyTransform(t); } + + if (mode & IsFixed) + transformState.move(toLayoutSize(frameView().scrollPositionRespectingCustomFixedPosition())); } bool RenderView::requiresColumns(int) const @@ -471,11 +492,6 @@ void RenderView::computeColumnCountAndWidth() setComputedColumnCountAndWidth(1, columnWidth); } -ColumnInfo::PaginationUnit RenderView::paginationUnit() const -{ - return frameView().pagination().behavesLikeColumns ? ColumnInfo::Column : ColumnInfo::Page; -} - void RenderView::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) { // If we ever require layout but receive a paint anyway, something has gone horribly wrong. @@ -485,31 +501,50 @@ void RenderView::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset) // This avoids painting garbage between columns if there is a column gap. if (frameView().pagination().mode != Pagination::Unpaginated && paintInfo.shouldPaintWithinRoot(*this)) - paintInfo.context->fillRect(paintInfo.rect, frameView().baseBackgroundColor(), ColorSpaceDeviceRGB); + paintInfo.context().fillRect(paintInfo.rect, frameView().baseBackgroundColor()); paintObject(paintInfo, paintOffset); } -static inline bool isComposited(RenderElement* object) +RenderElement* RenderView::rendererForRootBackground() const { - return object->hasLayer() && toRenderLayerModelObject(object)->layer()->isComposited(); + auto* firstChild = this->firstChild(); + if (!firstChild) + return nullptr; + ASSERT(is<RenderElement>(*firstChild)); + auto& documentRenderer = downcast<RenderElement>(*firstChild); + + if (documentRenderer.hasBackground()) + return &documentRenderer; + + // We propagate the background only for HTML content. + if (!is<HTMLHtmlElement>(documentRenderer.element())) + return &documentRenderer; + + if (auto* body = document().body()) { + if (auto* renderer = body->renderer()) + return renderer; + } + return &documentRenderer; } -static inline bool rendererObscuresBackground(RenderElement* rootObject) +static inline bool rendererObscuresBackground(const RenderElement& rootElement) { - if (!rootObject) + auto& style = rootElement.style(); + if (style.visibility() != VISIBLE || style.opacity() != 1 || style.hasTransform()) return false; - - const RenderStyle& style = rootObject->style(); - if (style.visibility() != VISIBLE - || style.opacity() != 1 - || style.hasTransform()) + + if (style.hasBorderRadius()) return false; - - if (isComposited(rootObject)) + + if (rootElement.isComposited()) + return false; + + auto* rendererForBackground = rootElement.view().rendererForRootBackground(); + if (!rendererForBackground) return false; - if (rootObject->rendererForRootBackground().style().backgroundClip() == TextFillBox) + if (rendererForBackground->style().backgroundClip() == TextFillBox) return false; return true; @@ -525,22 +560,19 @@ void RenderView::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint&) // layers with reflections, or transformed layers. // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being inside // a transform, transparency layer, etc. - Element* elt; - for (elt = document().ownerElement(); elt && elt->renderer(); elt = elt->document().ownerElement()) { - RenderLayer* layer = elt->renderer()->enclosingLayer(); + for (HTMLFrameOwnerElement* element = document().ownerElement(); element && element->renderer(); element = element->document().ownerElement()) { + RenderLayer* layer = element->renderer()->enclosingLayer(); if (layer->cannotBlitToWindow()) { frameView().setCannotBlitToWindow(); break; } -#if USE(ACCELERATED_COMPOSITING) if (RenderLayer* compositingLayer = layer->enclosingCompositingLayerForRepaint()) { if (!compositingLayer->backing()->paintsIntoWindow()) { frameView().setCannotBlitToWindow(); break; } } -#endif } if (document().ownerElement()) @@ -552,40 +584,41 @@ void RenderView::paintBoxDecorations(PaintInfo& paintInfo, const LayoutPoint&) bool rootFillsViewport = false; bool rootObscuresBackground = false; Element* documentElement = document().documentElement(); - if (RenderElement* rootRenderer = documentElement ? documentElement->renderer() : 0) { + if (RenderElement* rootRenderer = documentElement ? documentElement->renderer() : nullptr) { // The document element's renderer is currently forced to be a block, but may not always be. - RenderBox* rootBox = rootRenderer->isBox() ? toRenderBox(rootRenderer) : 0; + RenderBox* rootBox = is<RenderBox>(*rootRenderer) ? downcast<RenderBox>(rootRenderer) : nullptr; rootFillsViewport = rootBox && !rootBox->x() && !rootBox->y() && rootBox->width() >= width() && rootBox->height() >= height(); - rootObscuresBackground = rendererObscuresBackground(rootRenderer); + rootObscuresBackground = rendererObscuresBackground(*rootRenderer); } - bool hasTiledMargin = false; -#if USE(ACCELERATED_COMPOSITING) - hasTiledMargin = compositor().mainFrameBackingIsTiledWithMargin(); -#endif + bool backgroundShouldExtendBeyondPage = settings().backgroundShouldExtendBeyondPage(); + compositor().setRootExtendedBackgroundColor(backgroundShouldExtendBeyondPage ? frameView().documentBackgroundColor() : Color()); Page* page = document().page(); float pageScaleFactor = page ? page->pageScaleFactor() : 1; // If painting will entirely fill the view, no need to fill the background. - if (!hasTiledMargin && rootFillsViewport && rootObscuresBackground && pageScaleFactor >= 1) + if (rootFillsViewport && rootObscuresBackground && pageScaleFactor >= 1) return; // This code typically only executes if the root element's visibility has been set to hidden, // if there is a transform on the <html>, or if there is a page scale factor less than 1. - // Only fill with the base background color (typically white) if we're the root document, + // Only fill with a background color (typically white) if we're the root document, // since iframes/frames with no background in the child document should show the parent's background. + // We use the base background color unless the backgroundShouldExtendBeyondPage setting is set, + // in which case we use the document's background color. if (frameView().isTransparent()) // FIXME: This needs to be dynamic. We should be able to go back to blitting if we ever stop being transparent. frameView().setCannotBlitToWindow(); // The parent must show behind the child. else { - Color backgroundColor = hasTiledMargin ? frameView().documentBackgroundColor() : frameView().baseBackgroundColor(); - if (backgroundColor.alpha()) { - CompositeOperator previousOperator = paintInfo.context->compositeOperation(); - paintInfo.context->setCompositeOperation(CompositeCopy); - paintInfo.context->fillRect(paintInfo.rect, backgroundColor, style().colorSpace()); - paintInfo.context->setCompositeOperation(previousOperator); + const Color& documentBackgroundColor = frameView().documentBackgroundColor(); + const Color& backgroundColor = (backgroundShouldExtendBeyondPage && documentBackgroundColor.isValid()) ? documentBackgroundColor : frameView().baseBackgroundColor(); + if (backgroundColor.isVisible()) { + CompositeOperator previousOperator = paintInfo.context().compositeOperation(); + paintInfo.context().setCompositeOperation(CompositeCopy); + paintInfo.context().fillRect(paintInfo.rect, backgroundColor); + paintInfo.context().setCompositeOperation(previousOperator); } else - paintInfo.context->clearRect(paintInfo.rect); + paintInfo.context().clearRect(paintInfo.rect); } } @@ -596,46 +629,57 @@ bool RenderView::shouldRepaint(const LayoutRect& rect) const void RenderView::repaintRootContents() { -#if USE(ACCELERATED_COMPOSITING) if (layer()->isComposited()) { layer()->setBackingNeedsRepaint(GraphicsLayer::DoNotClipToLayer); return; } -#endif - repaint(); + + // Always use layoutOverflowRect() to fix rdar://problem/27182267. + // This should be cleaned up via webkit.org/b/159913 and webkit.org/b/159914. + RenderLayerModelObject* repaintContainer = containerForRepaint(); + repaintUsingContainer(repaintContainer, computeRectForRepaint(layoutOverflowRect(), repaintContainer)); } -void RenderView::repaintViewRectangle(const LayoutRect& repaintRect, bool immediate) const +void RenderView::repaintViewRectangle(const LayoutRect& repaintRect) const { - // FIXME: Get rid of the 'immediate' argument. It only works on Mac WK1 and should never be used. if (!shouldRepaint(repaintRect)) return; + // FIXME: enclosingRect is needed as long as we integral snap ScrollView/FrameView/RenderWidget size/position. + IntRect enclosingRect = enclosingIntRect(repaintRect); if (auto ownerElement = document().ownerElement()) { - if (!ownerElement->renderer()) + RenderBox* ownerBox = ownerElement->renderBox(); + if (!ownerBox) return; - auto& ownerBox = toRenderBox(*ownerElement->renderer()); LayoutRect viewRect = this->viewRect(); #if PLATFORM(IOS) // Don't clip using the visible rect since clipping is handled at a higher level on iPhone. - LayoutRect adjustedRect = repaintRect; + LayoutRect adjustedRect = enclosingRect; #else - LayoutRect adjustedRect = intersection(repaintRect, viewRect); + LayoutRect adjustedRect = intersection(enclosingRect, viewRect); #endif adjustedRect.moveBy(-viewRect.location()); - adjustedRect.moveBy(ownerBox.contentBoxRect().location()); - ownerBox.repaintRectangle(adjustedRect, immediate); + adjustedRect.moveBy(ownerBox->contentBoxRect().location()); + + // A dirty rect in an iframe is relative to the contents of that iframe. + // When we traverse between parent frames and child frames, we need to make sure + // that the coordinate system is mapped appropriately between the iframe's contents + // and the Renderer that contains the iframe. This transformation must account for a + // left scrollbar (if one exists). + FrameView& frameView = this->frameView(); + if (frameView.shouldPlaceBlockDirectionScrollbarOnLeft() && frameView.verticalScrollbar()) + adjustedRect.move(LayoutSize(frameView.verticalScrollbar()->occupiedWidth(), 0)); + + ownerBox->repaintRectangle(adjustedRect); return; } - IntRect pixelSnappedRect = pixelSnappedIntRect(repaintRect); - frameView().addTrackedRepaintRect(pixelSnappedRect); - - if (!m_accumulatedRepaintRegion || immediate) { - frameView().repaintContentRectangle(pixelSnappedRect, immediate); + frameView().addTrackedRepaintRect(snapRectToDevicePixels(repaintRect, document().deviceScaleFactor())); + if (!m_accumulatedRepaintRegion) { + frameView().repaintContentRectangle(enclosingRect); return; } - m_accumulatedRepaintRegion->unite(pixelSnappedRect); + m_accumulatedRepaintRegion->unite(enclosingRect); // Region will get slow if it gets too complex. Merge all rects so far to bounds if this happens. // FIXME: Maybe there should be a region type that does this automatically. @@ -650,34 +694,17 @@ void RenderView::flushAccumulatedRepaintRegion() const ASSERT(m_accumulatedRepaintRegion); auto repaintRects = m_accumulatedRepaintRegion->rects(); for (auto& rect : repaintRects) - frameView().repaintContentRectangle(rect, false); + frameView().repaintContentRectangle(rect); m_accumulatedRepaintRegion = nullptr; } -void RenderView::repaintRectangleInViewAndCompositedLayers(const LayoutRect& ur, bool immediate) -{ - if (!shouldRepaint(ur)) - return; - - repaintViewRectangle(ur, immediate); - -#if USE(ACCELERATED_COMPOSITING) - RenderLayerCompositor& compositor = this->compositor(); - if (compositor.inCompositingMode()) { - IntRect repaintRect = pixelSnappedIntRect(ur); - compositor.repaintCompositedLayers(&repaintRect); - } -#endif -} - void RenderView::repaintViewAndCompositedLayers() { repaintRootContents(); -#if USE(ACCELERATED_COMPOSITING) + RenderLayerCompositor& compositor = this->compositor(); if (compositor.inCompositingMode()) compositor.repaintCompositedLayers(); -#endif } LayoutRect RenderView::visualOverflowRect() const @@ -688,40 +715,45 @@ LayoutRect RenderView::visualOverflowRect() const return RenderBlockFlow::visualOverflowRect(); } -void RenderView::computeRectForRepaint(const RenderLayerModelObject* repaintContainer, LayoutRect& rect, bool fixed) const +LayoutRect RenderView::computeRectForRepaint(const LayoutRect& rect, const RenderLayerModelObject* repaintContainer, RepaintContext context) const { - // If a container was specified, and was not 0 or the RenderView, + // If a container was specified, and was not nullptr or the RenderView, // then we should have found it by now. ASSERT_ARG(repaintContainer, !repaintContainer || repaintContainer == this); if (printing()) - return; - + return rect; + + LayoutRect adjustedRect = rect; if (style().isFlippedBlocksWritingMode()) { // We have to flip by hand since the view's logical height has not been determined. We // can use the viewport width and height. if (style().isHorizontalWritingMode()) - rect.setY(viewHeight() - rect.maxY()); + adjustedRect.setY(viewHeight() - adjustedRect.maxY()); else - rect.setX(viewWidth() - rect.maxX()); + adjustedRect.setX(viewWidth() - adjustedRect.maxX()); } - if (fixed) { -#if PLATFORM(IOS) - rect.move(fixedPositionOffset(frameView())); -#else - rect.move(frameView().scrollOffsetForFixedPosition()); -#endif - } - + if (context.m_hasPositionFixedDescendant) + adjustedRect.moveBy(frameView().scrollPositionRespectingCustomFixedPosition()); + // Apply our transform if we have one (because of full page zooming). if (!repaintContainer && layer() && layer()->transform()) - rect = layer()->transform()->mapRect(rect); + adjustedRect = LayoutRect(layer()->transform()->mapRect(snapRectToDevicePixels(adjustedRect, document().deviceScaleFactor()))); + return adjustedRect; +} + +bool RenderView::isScrollableOrRubberbandableBox() const +{ + // The main frame might be allowed to rubber-band even if there is no content to scroll to. This is unique to + // the main frame; subframes and overflow areas have to have content that can be scrolled to in order to rubber-band. + FrameView::Scrollability defineScrollable = frame().ownerElement() ? FrameView::Scrollability::Scrollable : FrameView::Scrollability::ScrollableOrRubberbandable; + return frameView().isScrollable(defineScrollable); } void RenderView::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumulatedOffset) const { - rects.append(pixelSnappedIntRect(accumulatedOffset, layer()->size())); + rects.append(snappedIntRect(accumulatedOffset, layer()->size())); } void RenderView::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const @@ -734,7 +766,7 @@ void RenderView::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const static RenderObject* rendererAfterPosition(RenderObject* object, unsigned offset) { if (!object) - return 0; + return nullptr; RenderObject* child = object->childAt(offset); return child ? child : object->nextInPreOrderAfterChildren(); @@ -742,26 +774,44 @@ static RenderObject* rendererAfterPosition(RenderObject* object, unsigned offset IntRect RenderView::selectionBounds(bool clipToVisibleContent) const { - typedef HashMap<RenderObject*, OwnPtr<RenderSelectionInfo>> SelectionMap; + LayoutRect selRect = subtreeSelectionBounds(*this, clipToVisibleContent); + + if (hasRenderNamedFlowThreads()) { + for (auto* namedFlowThread : *m_flowThreadController->renderNamedFlowThreadList()) { + LayoutRect currRect = subtreeSelectionBounds(*namedFlowThread, clipToVisibleContent); + selRect.unite(currRect); + } + } + + return snappedIntRect(selRect); +} + +LayoutRect RenderView::subtreeSelectionBounds(const SelectionSubtreeRoot& root, bool clipToVisibleContent) const +{ + typedef HashMap<RenderObject*, std::unique_ptr<RenderSelectionInfo>> SelectionMap; SelectionMap selectedObjects; - RenderObject* os = m_selectionStart; - RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); + RenderObject* os = root.selectionData().selectionStart(); + auto* selectionEnd = root.selectionData().selectionEnd(); + RenderObject* stop = nullptr; + if (selectionEnd) + stop = rendererAfterPosition(selectionEnd, root.selectionData().selectionEndPos().value()); + SelectionIterator selectionIterator(os); while (os && os != stop) { - if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { + if ((os->canBeSelectionLeaf() || os == root.selectionData().selectionStart() || os == root.selectionData().selectionEnd()) && os->selectionState() != SelectionNone) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. - selectedObjects.set(os, adoptPtr(new RenderSelectionInfo(os, clipToVisibleContent))); + selectedObjects.set(os, std::make_unique<RenderSelectionInfo>(*os, clipToVisibleContent)); RenderBlock* cb = os->containingBlock(); - while (cb && !cb->isRenderView()) { - OwnPtr<RenderSelectionInfo>& blockInfo = selectedObjects.add(cb, nullptr).iterator->value; + while (cb && !is<RenderView>(*cb)) { + std::unique_ptr<RenderSelectionInfo>& blockInfo = selectedObjects.add(cb, nullptr).iterator->value; if (blockInfo) break; - blockInfo = adoptPtr(new RenderSelectionInfo(cb, clipToVisibleContent)); + blockInfo = std::make_unique<RenderSelectionInfo>(*cb, clipToVisibleContent); cb = cb->containingBlock(); } } - os = os->nextInPreOrder(); + os = selectionIterator.next(); } // Now create a single bounding box rect that encloses the whole selection. @@ -777,149 +827,222 @@ IntRect RenderView::selectionBounds(bool clipToVisibleContent) const } selRect.unite(currRect); } - return pixelSnappedIntRect(selRect); + return selRect; } void RenderView::repaintSelection() const { + repaintSubtreeSelection(*this); + + if (hasRenderNamedFlowThreads()) { + for (auto* namedFlowThread : *m_flowThreadController->renderNamedFlowThreadList()) + repaintSubtreeSelection(*namedFlowThread); + } +} + +void RenderView::repaintSubtreeSelection(const SelectionSubtreeRoot& root) const +{ HashSet<RenderBlock*> processedBlocks; - RenderObject* end = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); - for (RenderObject* o = m_selectionStart; o && o != end; o = o->nextInPreOrder()) { - if (!o->canBeSelectionLeaf() && o != m_selectionStart && o != m_selectionEnd) + auto* selectionEnd = root.selectionData().selectionEnd(); + RenderObject* end = nullptr; + if (selectionEnd) + end = rendererAfterPosition(selectionEnd, root.selectionData().selectionEndPos().value()); + SelectionIterator selectionIterator(root.selectionData().selectionStart()); + for (RenderObject* o = selectionIterator.current(); o && o != end; o = selectionIterator.next()) { + if (!o->canBeSelectionLeaf() && o != root.selectionData().selectionStart() && o != root.selectionData().selectionEnd()) continue; if (o->selectionState() == SelectionNone) continue; - RenderSelectionInfo(o, true).repaint(); + RenderSelectionInfo(*o, true).repaint(); // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. - for (RenderBlock* block = o->containingBlock(); block && !block->isRenderView(); block = block->containingBlock()) { + for (RenderBlock* block = o->containingBlock(); block && !is<RenderView>(*block); block = block->containingBlock()) { if (!processedBlocks.add(block).isNewEntry) break; - RenderSelectionInfo(block, true).repaint(); + RenderSelectionInfo(*block, true).repaint(); } } } -#if USE(ACCELERATED_COMPOSITING) -// Compositing layer dimensions take outline size into account, so we have to recompute layer -// bounds when it changes. -// FIXME: This is ugly; it would be nice to have a better way to do this. -void RenderView::setMaximalOutlineSize(int o) +void RenderView::setSelection(RenderObject* start, std::optional<unsigned> startPos, RenderObject* end, std::optional<unsigned> endPos, SelectionRepaintMode blockRepaintMode) { - if (o != m_maximalOutlineSize) { - m_maximalOutlineSize = o; + // Make sure both our start and end objects are defined. + // Check www.msnbc.com and try clicking around to find the case where this happened. + if ((start && !end) || (end && !start)) + return; - // maximalOutlineSize affects compositing layer dimensions. - compositor().setCompositingLayersNeedRebuild(); // FIXME: this really just needs to be a geometry update. + bool caretChanged = m_selectionWasCaret != frame().selection().isCaret(); + m_selectionWasCaret = frame().selection().isCaret(); + // Just return if the selection hasn't changed. + if (m_selectionUnsplitStart == start && m_selectionUnsplitStartPos == startPos + && m_selectionUnsplitEnd == end && m_selectionUnsplitEndPos == endPos && !caretChanged) { + return; } + +#if ENABLE(SERVICE_CONTROLS) + // Clear the current rects and create a notifier for the new rects we are about to gather. + // The Notifier updates the Editor when it goes out of scope and is destroyed. + std::unique_ptr<SelectionRectGatherer::Notifier> rectNotifier = m_selectionRectGatherer.clearAndCreateNotifier(); +#endif // ENABLE(SERVICE_CONTROLS) + // Set global positions for new selection. + m_selectionUnsplitStart = start; + m_selectionUnsplitStartPos = startPos; + m_selectionUnsplitEnd = end; + m_selectionUnsplitEndPos = endPos; + + // If there is no RenderNamedFlowThreads we follow the regular selection. + if (!hasRenderNamedFlowThreads()) { + RenderSubtreesMap singleSubtreeMap; + singleSubtreeMap.set(this, SelectionSubtreeData(start, startPos, end, endPos)); + updateSelectionForSubtrees(singleSubtreeMap, blockRepaintMode); + return; + } + + splitSelectionBetweenSubtrees(start, startPos, end, endPos, blockRepaintMode); } -#endif -// When exploring the RenderTree looking for the nodes involved in the Selection, sometimes it's -// required to change the traversing direction because the "start" position is below the "end" one. -static inline RenderObject* getNextOrPrevRenderObjectBasedOnDirection(const RenderObject* o, const RenderObject* stop, bool& continueExploring, bool& exploringBackwards) -{ - RenderObject* next; - if (exploringBackwards) { - next = o->previousInPreOrder(); - continueExploring = next && !(next)->isRenderView(); - } else { - next = o->nextInPreOrder(); - continueExploring = next && next != stop; - exploringBackwards = !next && (next != stop); - if (exploringBackwards) { - next = stop->previousInPreOrder(); - continueExploring = next && !next->isRenderView(); +void RenderView::splitSelectionBetweenSubtrees(const RenderObject* start, std::optional<unsigned> startPos, const RenderObject* end, std::optional<unsigned> endPos, SelectionRepaintMode blockRepaintMode) +{ + // Compute the visible selection end points for each of the subtrees. + RenderSubtreesMap renderSubtreesMap; + + SelectionSubtreeData initialSelection; + renderSubtreesMap.set(this, initialSelection); + for (auto* namedFlowThread : *flowThreadController().renderNamedFlowThreadList()) + renderSubtreesMap.set(namedFlowThread, initialSelection); + + if (start && end) { + Node* startNode = start->node(); + Node* endNode = end->node(); + ASSERT(endNode); + Node* stopNode = NodeTraversal::nextSkippingChildren(*endNode); + + for (Node* node = startNode; node != stopNode; node = NodeTraversal::next(*node)) { + RenderObject* renderer = node->renderer(); + if (!renderer) + continue; + + SelectionSubtreeRoot& root = renderer->selectionRoot(); + SelectionSubtreeData selectionData = renderSubtreesMap.get(&root); + if (selectionData.selectionClear()) { + selectionData.setSelectionStart(node->renderer()); + selectionData.setSelectionStartPos(node == startNode ? startPos : std::optional<unsigned>(0)); + } + + selectionData.setSelectionEnd(node->renderer()); + if (node == endNode) + selectionData.setSelectionEndPos(endPos); + else { + unsigned newEndPos = node->offsetInCharacters() ? node->maxCharacterOffset() : node->countChildNodes(); + selectionData.setSelectionEndPos(newEndPos); + } + + renderSubtreesMap.set(&root, selectionData); } } - - return next; + + updateSelectionForSubtrees(renderSubtreesMap, blockRepaintMode); } -void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* end, int endPos, SelectionRepaintMode blockRepaintMode) +void RenderView::updateSelectionForSubtrees(RenderSubtreesMap& renderSubtreesMap, SelectionRepaintMode blockRepaintMode) { - // Make sure both our start and end objects are defined. - // Check www.msnbc.com and try clicking around to find the case where this happened. - if ((start && !end) || (end && !start)) - return; + SubtreeOldSelectionDataMap oldSelectionDataMap; + for (auto& subtreeSelectionInfo : renderSubtreesMap) { + SelectionSubtreeRoot& root = *subtreeSelectionInfo.key; + std::unique_ptr<OldSelectionData> oldSelectionData = std::make_unique<OldSelectionData>(); - bool caretChanged = m_selectionWasCaret != view().frame().selection().isCaret(); - m_selectionWasCaret = view().frame().selection().isCaret(); - // Just return if the selection hasn't changed. - if (m_selectionStart == start && m_selectionStartPos == startPos && - m_selectionEnd == end && m_selectionEndPos == endPos && !caretChanged) - return; + clearSubtreeSelection(root, blockRepaintMode, *oldSelectionData); + oldSelectionDataMap.set(&root, WTFMove(oldSelectionData)); - // Record the old selected objects. These will be used later - // when we compare against the new selected objects. - int oldStartPos = m_selectionStartPos; - int oldEndPos = m_selectionEndPos; + root.setSelectionData(subtreeSelectionInfo.value); + if (hasRenderNamedFlowThreads()) + root.adjustForVisibleSelection(document()); + } - // Objects each have a single selection rect to examine. - typedef HashMap<RenderObject*, OwnPtr<RenderSelectionInfo>> SelectedObjectMap; - SelectedObjectMap oldSelectedObjects; - SelectedObjectMap newSelectedObjects; + // Update selection status for the objects inside the selection subtrees. + // This needs to be done after the previous loop updated the selectionStart/End + // parameters of all subtrees because we're going to be climbing up the containing + // block chain and we might end up in a different selection subtree. + for (const auto* subtreeSelectionRoot : renderSubtreesMap.keys()) { + OldSelectionData& oldSelectionData = *oldSelectionDataMap.get(subtreeSelectionRoot); + applySubtreeSelection(*subtreeSelectionRoot, blockRepaintMode, oldSelectionData); + } +} +static inline bool isValidObjectForNewSelection(const SelectionSubtreeRoot& root, const RenderObject& object) +{ + return (object.canBeSelectionLeaf() || &object == root.selectionData().selectionStart() || &object == root.selectionData().selectionEnd()) && object.selectionState() != RenderObject::SelectionNone && object.containingBlock(); +} + +void RenderView::clearSubtreeSelection(const SelectionSubtreeRoot& root, SelectionRepaintMode blockRepaintMode, OldSelectionData& oldSelectionData) const +{ + // Record the old selected objects. These will be used later + // when we compare against the new selected objects. + oldSelectionData.selectionStartPos = root.selectionData().selectionStartPos(); + oldSelectionData.selectionEndPos = root.selectionData().selectionEndPos(); + // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks. // In order to get the repaint rect right, we have to examine left, middle, and right rects individually, since otherwise // the union of those rects might remain the same even when changes have occurred. - typedef HashMap<RenderBlock*, OwnPtr<RenderBlockSelectionInfo>> SelectedBlockMap; - SelectedBlockMap oldSelectedBlocks; - SelectedBlockMap newSelectedBlocks; - RenderObject* os = m_selectionStart; - RenderObject* stop = rendererAfterPosition(m_selectionEnd, m_selectionEndPos); - bool exploringBackwards = false; - bool continueExploring = os && (os != stop); - while (continueExploring) { - if ((os->canBeSelectionLeaf() || os == m_selectionStart || os == m_selectionEnd) && os->selectionState() != SelectionNone) { + RenderObject* os = root.selectionData().selectionStart(); + auto* selectionEnd = root.selectionData().selectionEnd(); + RenderObject* stop = nullptr; + if (selectionEnd) + stop = rendererAfterPosition(selectionEnd, root.selectionData().selectionEndPos().value()); + SelectionIterator selectionIterator(os); + while (os && os != stop) { + if (isValidObjectForNewSelection(root, *os)) { // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well. - oldSelectedObjects.set(os, adoptPtr(new RenderSelectionInfo(os, true))); + oldSelectionData.selectedObjects.set(os, std::make_unique<RenderSelectionInfo>(*os, true)); if (blockRepaintMode == RepaintNewXOROld) { RenderBlock* cb = os->containingBlock(); - while (cb && !cb->isRenderView()) { - OwnPtr<RenderBlockSelectionInfo>& blockInfo = oldSelectedBlocks.add(cb, nullptr).iterator->value; + while (cb && !is<RenderView>(*cb)) { + std::unique_ptr<RenderBlockSelectionInfo>& blockInfo = oldSelectionData.selectedBlocks.add(cb, nullptr).iterator->value; if (blockInfo) break; - blockInfo = adoptPtr(new RenderBlockSelectionInfo(cb)); + blockInfo = std::make_unique<RenderBlockSelectionInfo>(*cb); cb = cb->containingBlock(); } } } - os = getNextOrPrevRenderObjectBasedOnDirection(os, stop, continueExploring, exploringBackwards); + os = selectionIterator.next(); } - // Now clear the selection. - SelectedObjectMap::iterator oldObjectsEnd = oldSelectedObjects.end(); - for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) - i->key->setSelectionStateIfNeeded(SelectionNone); - - // set selection start and end - m_selectionStart = start; - m_selectionStartPos = startPos; - m_selectionEnd = end; - m_selectionEndPos = endPos; + for (auto* selectedObject : oldSelectionData.selectedObjects.keys()) + selectedObject->setSelectionStateIfNeeded(SelectionNone); +} - // Update the selection status of all objects between m_selectionStart and m_selectionEnd - if (start && start == end) - start->setSelectionStateIfNeeded(SelectionBoth); +void RenderView::applySubtreeSelection(const SelectionSubtreeRoot& root, SelectionRepaintMode blockRepaintMode, const OldSelectionData& oldSelectionData) +{ + // Update the selection status of all objects between selectionStart and selectionEnd + if (root.selectionData().selectionStart() && root.selectionData().selectionStart() == root.selectionData().selectionEnd()) + root.selectionData().selectionStart()->setSelectionStateIfNeeded(SelectionBoth); else { - if (start) - start->setSelectionStateIfNeeded(SelectionStart); - if (end) - end->setSelectionStateIfNeeded(SelectionEnd); + if (root.selectionData().selectionStart()) + root.selectionData().selectionStart()->setSelectionStateIfNeeded(SelectionStart); + if (root.selectionData().selectionEnd()) + root.selectionData().selectionEnd()->setSelectionStateIfNeeded(SelectionEnd); } - RenderObject* o = start; - stop = rendererAfterPosition(end, endPos); - - while (o && o != stop) { - if (o != start && o != end && o->canBeSelectionLeaf()) - o->setSelectionStateIfNeeded(SelectionInside); - o = o->nextInPreOrder(); + RenderObject* selectionStart = root.selectionData().selectionStart(); + auto* selectionDataEnd = root.selectionData().selectionEnd(); + RenderObject* selectionEnd = nullptr; + if (selectionDataEnd) + selectionEnd = rendererAfterPosition(selectionDataEnd, root.selectionData().selectionEndPos().value()); + SelectionIterator selectionIterator(selectionStart); + for (RenderObject* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) { + if (currentRenderer == root.selectionData().selectionStart() || currentRenderer == root.selectionData().selectionEnd()) + continue; + if (!currentRenderer->canBeSelectionLeaf()) + continue; + // FIXME: Move this logic to SelectionIterator::next() + if (¤tRenderer->selectionRoot() != &root) + continue; + currentRenderer->setSelectionStateIfNeeded(SelectionInside); } if (blockRepaintMode != RepaintNothing) @@ -927,36 +1050,48 @@ void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* e // Now that the selection state has been updated for the new objects, walk them again and // put them in the new objects list. - o = start; - exploringBackwards = false; - continueExploring = o && (o != stop); - while (continueExploring) { - if ((o->canBeSelectionLeaf() || o == start || o == end) && o->selectionState() != SelectionNone) { - newSelectedObjects.set(o, adoptPtr(new RenderSelectionInfo(o, true))); - RenderBlock* cb = o->containingBlock(); - while (cb && !cb->isRenderView()) { - OwnPtr<RenderBlockSelectionInfo>& blockInfo = newSelectedBlocks.add(cb, nullptr).iterator->value; + SelectedObjectMap newSelectedObjects; + SelectedBlockMap newSelectedBlocks; + selectionIterator = SelectionIterator(selectionStart); + for (RenderObject* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) { + if (isValidObjectForNewSelection(root, *currentRenderer)) { + std::unique_ptr<RenderSelectionInfo> selectionInfo = std::make_unique<RenderSelectionInfo>(*currentRenderer, true); + +#if ENABLE(SERVICE_CONTROLS) + for (auto& rect : selectionInfo->collectedSelectionRects()) + m_selectionRectGatherer.addRect(selectionInfo->repaintContainer(), rect); + if (!currentRenderer->isTextOrLineBreak()) + m_selectionRectGatherer.setTextOnly(false); +#endif + + newSelectedObjects.set(currentRenderer, WTFMove(selectionInfo)); + + RenderBlock* containingBlock = currentRenderer->containingBlock(); + while (containingBlock && !is<RenderView>(*containingBlock)) { + std::unique_ptr<RenderBlockSelectionInfo>& blockInfo = newSelectedBlocks.add(containingBlock, nullptr).iterator->value; if (blockInfo) break; - blockInfo = adoptPtr(new RenderBlockSelectionInfo(cb)); - cb = cb->containingBlock(); + blockInfo = std::make_unique<RenderBlockSelectionInfo>(*containingBlock); + containingBlock = containingBlock->containingBlock(); + +#if ENABLE(SERVICE_CONTROLS) + m_selectionRectGatherer.addGapRects(blockInfo->repaintContainer(), blockInfo->rects()); +#endif } } - - o = getNextOrPrevRenderObjectBasedOnDirection(o, stop, continueExploring, exploringBackwards); } if (blockRepaintMode == RepaintNothing) return; // Have any of the old selected objects changed compared to the new selection? - for (SelectedObjectMap::iterator i = oldSelectedObjects.begin(); i != oldObjectsEnd; ++i) { - RenderObject* obj = i->key; + for (const auto& selectedObjectInfo : oldSelectionData.selectedObjects) { + RenderObject* obj = selectedObjectInfo.key; RenderSelectionInfo* newInfo = newSelectedObjects.get(obj); - RenderSelectionInfo* oldInfo = i->value.get(); - if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state() || - (m_selectionStart == obj && oldStartPos != m_selectionStartPos) || - (m_selectionEnd == obj && oldEndPos != m_selectionEndPos)) { + RenderSelectionInfo* oldInfo = selectedObjectInfo.value.get(); + if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state() + || (root.selectionData().selectionStart() == obj && oldSelectionData.selectionStartPos != root.selectionData().selectionStartPos()) + || (root.selectionData().selectionEnd() == obj && oldSelectionData.selectionEndPos != root.selectionData().selectionEndPos())) { oldInfo->repaint(); if (newInfo) { newInfo->repaint(); @@ -966,16 +1101,14 @@ void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* e } // Any new objects that remain were not found in the old objects dict, and so they need to be updated. - SelectedObjectMap::iterator newObjectsEnd = newSelectedObjects.end(); - for (SelectedObjectMap::iterator i = newSelectedObjects.begin(); i != newObjectsEnd; ++i) - i->value->repaint(); + for (const auto& selectedObjectInfo : newSelectedObjects) + selectedObjectInfo.value->repaint(); // Have any of the old blocks changed? - SelectedBlockMap::iterator oldBlocksEnd = oldSelectedBlocks.end(); - for (SelectedBlockMap::iterator i = oldSelectedBlocks.begin(); i != oldBlocksEnd; ++i) { - RenderBlock* block = i->key; + for (const auto& selectedBlockInfo : oldSelectionData.selectedBlocks) { + const RenderBlock* block = selectedBlockInfo.key; RenderBlockSelectionInfo* newInfo = newSelectedBlocks.get(block); - RenderBlockSelectionInfo* oldInfo = i->value.get(); + RenderBlockSelectionInfo* oldInfo = selectedBlockInfo.value.get(); if (!newInfo || oldInfo->rects() != newInfo->rects() || oldInfo->state() != newInfo->state()) { oldInfo->repaint(); if (newInfo) { @@ -986,29 +1119,22 @@ void RenderView::setSelection(RenderObject* start, int startPos, RenderObject* e } // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated. - SelectedBlockMap::iterator newBlocksEnd = newSelectedBlocks.end(); - for (SelectedBlockMap::iterator i = newSelectedBlocks.begin(); i != newBlocksEnd; ++i) - i->value->repaint(); + for (const auto& selectedBlockInfo : newSelectedBlocks) + selectedBlockInfo.value->repaint(); } -void RenderView::getSelection(RenderObject*& startRenderer, int& startOffset, RenderObject*& endRenderer, int& endOffset) const +void RenderView::getSelection(RenderObject*& startRenderer, std::optional<unsigned>& startOffset, RenderObject*& endRenderer, std::optional<unsigned>& endOffset) const { - startRenderer = m_selectionStart; - startOffset = m_selectionStartPos; - endRenderer = m_selectionEnd; - endOffset = m_selectionEndPos; + startRenderer = m_selectionUnsplitStart; + startOffset = m_selectionUnsplitStartPos; + endRenderer = m_selectionUnsplitEnd; + endOffset = m_selectionUnsplitEndPos; } void RenderView::clearSelection() { layer()->repaintBlockSelectionGaps(); - setSelection(nullptr, -1, nullptr, -1, RepaintNewMinusOld); -} - -void RenderView::selectionStartEnd(int& startPos, int& endPos) const -{ - startPos = m_selectionStartPos; - endPos = m_selectionEndPos; + setSelection(nullptr, std::nullopt, nullptr, std::nullopt, RepaintNewMinusOld); } bool RenderView::printing() const @@ -1034,30 +1160,29 @@ IntRect RenderView::unscaledDocumentRect() const { LayoutRect overflowRect(layoutOverflowRect()); flipForWritingMode(overflowRect); - return pixelSnappedIntRect(overflowRect); + return snappedIntRect(overflowRect); } bool RenderView::rootBackgroundIsEntirelyFixed() const { - RenderElement* rootObject = document().documentElement() ? document().documentElement()->renderer() : 0; - if (!rootObject) - return false; - - return rootObject->rendererForRootBackground().hasEntirelyFixedBackground(); + if (auto* rootBackgroundRenderer = rendererForRootBackground()) + return rootBackgroundRenderer->style().hasEntirelyFixedBackground(); + return false; } - -LayoutRect RenderView::backgroundRect(RenderBox* backgroundRenderer) const + +LayoutRect RenderView::unextendedBackgroundRect() const { - if (!hasColumns()) - return frameView().hasExtendedBackground() ? frameView().extendedBackgroundRect() : unscaledDocumentRect(); - - ColumnInfo* columnInfo = this->columnInfo(); - LayoutRect backgroundRect(0, 0, columnInfo->desiredColumnWidth(), columnInfo->columnHeight() * columnInfo->columnCount()); - if (!isHorizontalWritingMode()) - backgroundRect = backgroundRect.transposedRect(); - backgroundRenderer->flipForWritingMode(backgroundRect); + // FIXME: What is this? Need to patch for new columns? + return unscaledDocumentRect(); +} + +LayoutRect RenderView::backgroundRect() const +{ + // FIXME: New columns care about this? + if (frameView().hasExtendedBackgroundRectForPainting()) + return frameView().extendedBackgroundRectForPainting(); - return backgroundRect; + return unextendedBackgroundRect(); } IntRect RenderView::documentRect() const @@ -1104,24 +1229,22 @@ void RenderView::pushLayoutState(RenderObject& root) ASSERT(m_layoutStateDisableCount == 0); ASSERT(m_layoutState == 0); - pushLayoutStateForCurrentFlowThread(root); m_layoutState = std::make_unique<LayoutState>(root); + pushLayoutStateForCurrentFlowThread(root); } -bool RenderView::shouldDisableLayoutStateForSubtree(RenderObject* renderer) const +void RenderView::pushLayoutStateForPagination(RenderBlockFlow& layoutRoot) { - RenderObject* o = renderer; - while (o) { - if (o->hasColumns() || o->hasTransform() || o->hasReflection()) - return true; - o = o->container(); - } - return false; + pushLayoutState(layoutRoot); + ASSERT(m_layoutState); + m_layoutState->m_isPaginated = true; + // This is just a flag for known page height (see RenderBlockFlow::checkForPaginationLogicalHeightChange). + m_layoutState->m_pageLogicalHeight = 1; } -IntSize RenderView::viewportSize() const +IntSize RenderView::viewportSizeForCSSViewportUnits() const { - return frameView().visibleContentRectIncludingScrollbars(ScrollableArea::LegacyIOSDocumentVisibleRect).size(); + return frameView().viewportSizeForCSSViewportUnits(); } void RenderView::updateHitTestResult(HitTestResult& result, const LayoutPoint& point) @@ -1129,6 +1252,9 @@ void RenderView::updateHitTestResult(HitTestResult& result, const LayoutPoint& p if (result.innerNode()) return; + if (multiColumnFlowThread() && multiColumnFlowThread()->firstMultiColumnSet()) + return multiColumnFlowThread()->firstMultiColumnSet()->updateHitTestResult(result, point); + Node* node = document().documentElement(); if (node) { result.setInnerNode(node); @@ -1160,14 +1286,13 @@ void RenderView::setBestTruncatedAt(int y, RenderBoxModelObject* forRenderer, bo } // Prefer the widest object that tries to move the pagination point - IntRect boundingBox = forRenderer->borderBoundingBox(); + LayoutRect boundingBox = forRenderer->borderBoundingBox(); if (boundingBox.width() > m_legacyPrinting.m_truncatorWidth) { m_legacyPrinting.m_truncatorWidth = boundingBox.width(); m_legacyPrinting.m_bestTruncatedAt = y; } } -#if USE(ACCELERATED_COMPOSITING) bool RenderView::usesCompositing() const { return m_compositor && m_compositor->inCompositingMode(); @@ -1176,20 +1301,15 @@ bool RenderView::usesCompositing() const RenderLayerCompositor& RenderView::compositor() { if (!m_compositor) - m_compositor = adoptPtr(new RenderLayerCompositor(*this)); + m_compositor = std::make_unique<RenderLayerCompositor>(*this); return *m_compositor; } -#endif void RenderView::setIsInWindow(bool isInWindow) { -#if USE(ACCELERATED_COMPOSITING) if (m_compositor) m_compositor->setIsInWindow(isInWindow); -#else - UNUSED_PARAM(isInWindow); -#endif } void RenderView::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) @@ -1197,6 +1317,8 @@ void RenderView::styleDidChange(StyleDifference diff, const RenderStyle* oldStyl RenderBlockFlow::styleDidChange(diff, oldStyle); if (hasRenderNamedFlowThreads()) flowThreadController().styleDidChange(); + + frameView().styleDidChange(); } bool RenderView::hasRenderNamedFlowThreads() const @@ -1212,38 +1334,22 @@ bool RenderView::checkTwoPassLayoutForAutoHeightRegions() const FlowThreadController& RenderView::flowThreadController() { if (!m_flowThreadController) - m_flowThreadController = FlowThreadController::create(this); + m_flowThreadController = std::make_unique<FlowThreadController>(this); return *m_flowThreadController; } -#if PLATFORM(IOS) -static bool isFixedPositionInViewport(const RenderObject& renderer, const RenderObject* container) -{ - return (renderer.style().position() == FixedPosition) && renderer.container() == container; -} - -bool RenderView::hasCustomFixedPosition(const RenderObject& renderer, ContainingBlockCheck checkContainer) const -{ - if (!frameView().useCustomFixedPositionLayoutRect()) - return false; - - if (checkContainer == CheckContainingBlock) - return isFixedPositionInViewport(renderer, this); - - return renderer.style().position() == FixedPosition; -} -#endif - void RenderView::pushLayoutStateForCurrentFlowThread(const RenderObject& object) { if (!m_flowThreadController) return; - RenderFlowThread* currentFlowThread = m_flowThreadController->currentRenderFlowThread(); + RenderFlowThread* currentFlowThread = object.flowThreadContainingBlock(); if (!currentFlowThread) return; + m_layoutState->setCurrentRenderFlowThread(currentFlowThread); + currentFlowThread->pushFlowThreadLayoutState(object); } @@ -1252,32 +1358,80 @@ void RenderView::popLayoutStateForCurrentFlowThread() if (!m_flowThreadController) return; - RenderFlowThread* currentFlowThread = m_flowThreadController->currentRenderFlowThread(); + RenderFlowThread* currentFlowThread = m_layoutState->currentRenderFlowThread(); if (!currentFlowThread) return; currentFlowThread->popFlowThreadLayoutState(); } -IntervalArena* RenderView::intervalArena() -{ - if (!m_intervalArena) - m_intervalArena = IntervalArena::create(); - return m_intervalArena.get(); -} - ImageQualityController& RenderView::imageQualityController() { if (!m_imageQualityController) - m_imageQualityController = ImageQualityController::create(*this); + m_imageQualityController = std::make_unique<ImageQualityController>(*this); return *m_imageQualityController; } +void RenderView::registerForVisibleInViewportCallback(RenderElement& renderer) +{ + ASSERT(!m_visibleInViewportRenderers.contains(&renderer)); + m_visibleInViewportRenderers.add(&renderer); +} + +void RenderView::unregisterForVisibleInViewportCallback(RenderElement& renderer) +{ + ASSERT(m_visibleInViewportRenderers.contains(&renderer)); + m_visibleInViewportRenderers.remove(&renderer); +} + +void RenderView::updateVisibleViewportRect(const IntRect& visibleRect) +{ + resumePausedImageAnimationsIfNeeded(visibleRect); + + for (auto* renderer : m_visibleInViewportRenderers) + renderer->visibleInViewportStateChanged(visibleRect.intersects(enclosingIntRect(renderer->absoluteClippedOverflowRect())) ? RenderElement::VisibleInViewport : RenderElement::NotVisibleInViewport); +} + +void RenderView::addRendererWithPausedImageAnimations(RenderElement& renderer) +{ + if (renderer.hasPausedImageAnimations()) { + ASSERT(m_renderersWithPausedImageAnimation.contains(&renderer)); + return; + } + renderer.setHasPausedImageAnimations(true); + m_renderersWithPausedImageAnimation.add(&renderer); +} + +void RenderView::removeRendererWithPausedImageAnimations(RenderElement& renderer) +{ + ASSERT(renderer.hasPausedImageAnimations()); + ASSERT(m_renderersWithPausedImageAnimation.contains(&renderer)); + + renderer.setHasPausedImageAnimations(false); + m_renderersWithPausedImageAnimation.remove(&renderer); +} + +void RenderView::resumePausedImageAnimationsIfNeeded(IntRect visibleRect) +{ + Vector<RenderElement*, 10> toRemove; + for (auto* renderer : m_renderersWithPausedImageAnimation) { + if (renderer->repaintForPausedImageAnimationsIfNeeded(visibleRect)) + toRemove.append(renderer); + } + for (auto& renderer : toRemove) + removeRendererWithPausedImageAnimations(*renderer); +} + RenderView::RepaintRegionAccumulator::RepaintRegionAccumulator(RenderView* view) - : m_rootView(view ? view->document().topDocument().renderView() : nullptr) { - if (!m_rootView) + if (!view) + return; + + auto* rootRenderView = view->document().topDocument().renderView(); + if (!rootRenderView) return; + + m_rootView = rootRenderView->createWeakPtr(); m_wasAccumulatingRepaintRegion = !!m_rootView->m_accumulatedRepaintRegion; if (!m_wasAccumulatingRepaintRegion) m_rootView->m_accumulatedRepaintRegion = std::make_unique<Region>(); @@ -1292,4 +1446,54 @@ RenderView::RepaintRegionAccumulator::~RepaintRegionAccumulator() m_rootView->flushAccumulatedRepaintRegion(); } +unsigned RenderView::pageNumberForBlockProgressionOffset(int offset) const +{ + int columnNumber = 0; + const Pagination& pagination = page().pagination(); + if (pagination.mode == Pagination::Unpaginated) + return columnNumber; + + bool progressionIsInline = false; + bool progressionIsReversed = false; + + if (multiColumnFlowThread()) { + progressionIsInline = multiColumnFlowThread()->progressionIsInline(); + progressionIsReversed = multiColumnFlowThread()->progressionIsReversed(); + } else + return columnNumber; + + if (!progressionIsInline) { + if (!progressionIsReversed) + columnNumber = (pagination.pageLength + pagination.gap - offset) / (pagination.pageLength + pagination.gap); + else + columnNumber = offset / (pagination.pageLength + pagination.gap); + } + + return columnNumber; +} + +unsigned RenderView::pageCount() const +{ + const Pagination& pagination = page().pagination(); + if (pagination.mode == Pagination::Unpaginated) + return 0; + + if (multiColumnFlowThread() && multiColumnFlowThread()->firstMultiColumnSet()) + return multiColumnFlowThread()->firstMultiColumnSet()->columnCount(); + + return 0; +} + +#if ENABLE(CSS_SCROLL_SNAP) +void RenderView::registerBoxWithScrollSnapPositions(const RenderBox& box) +{ + m_boxesWithScrollSnapPositions.add(&box); +} + +void RenderView::unregisterBoxWithScrollSnapPositions(const RenderBox& box) +{ + m_boxesWithScrollSnapPositions.remove(&box); +} +#endif + } // namespace WebCore |