diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/page/FrameView.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/page/FrameView.cpp')
-rw-r--r-- | Source/WebCore/page/FrameView.cpp | 3129 |
1 files changed, 2085 insertions, 1044 deletions
diff --git a/Source/WebCore/page/FrameView.cpp b/Source/WebCore/page/FrameView.cpp index 804c0420f..725c62f32 100644 --- a/Source/WebCore/page/FrameView.cpp +++ b/Source/WebCore/page/FrameView.cpp @@ -3,7 +3,7 @@ * 1999 Lars Knoll <knoll@kde.org> * 1999 Antti Koivisto <koivisto@kde.org> * 2000 Dirk Mueller <mueller@kde.org> - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2004-2017 Apple Inc. All rights reserved. * (C) 2006 Graham Dennis (graham.dennis@gmail.com) * (C) 2006 Alexey Proskuryakov (ap@nypop.com) * Copyright (C) 2009 Google Inc. All rights reserved. @@ -28,40 +28,55 @@ #include "FrameView.h" #include "AXObjectCache.h" -#include "AnimationController.h" #include "BackForwardController.h" +#include "CSSAnimationController.h" #include "CachedImage.h" #include "CachedResourceLoader.h" #include "Chrome.h" #include "ChromeClient.h" #include "DOMWindow.h" +#include "DebugPageOverlays.h" #include "DocumentMarkerController.h" #include "EventHandler.h" +#include "EventNames.h" #include "FloatRect.h" #include "FocusController.h" -#include "FontCache.h" -#include "FontLoader.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameSelection.h" #include "FrameTree.h" #include "GraphicsContext.h" +#include "HTMLBodyElement.h" #include "HTMLDocument.h" +#include "HTMLEmbedElement.h" #include "HTMLFrameElement.h" #include "HTMLFrameSetElement.h" +#include "HTMLHtmlElement.h" +#include "HTMLIFrameElement.h" #include "HTMLNames.h" +#include "HTMLObjectElement.h" #include "HTMLPlugInImageElement.h" +#include "ImageDocument.h" #include "InspectorClient.h" #include "InspectorController.h" #include "InspectorInstrumentation.h" +#include "Logging.h" #include "MainFrame.h" +#include "MemoryCache.h" +#include "MemoryPressureHandler.h" #include "OverflowEvent.h" +#include "Page.h" +#include "PageCache.h" +#include "PageOverlayController.h" #include "ProgressTracker.h" #include "RenderEmbeddedObject.h" #include "RenderFullScreen.h" #include "RenderIFrame.h" +#include "RenderInline.h" #include "RenderLayer.h" #include "RenderLayerBacking.h" +#include "RenderLayerCompositor.h" +#include "RenderSVGRoot.h" #include "RenderScrollbar.h" #include "RenderScrollbarPart.h" #include "RenderStyle.h" @@ -69,47 +84,35 @@ #include "RenderTheme.h" #include "RenderView.h" #include "RenderWidget.h" +#include "SVGDocument.h" +#include "SVGSVGElement.h" +#include "ScriptedAnimationController.h" #include "ScrollAnimator.h" #include "ScrollingCoordinator.h" #include "Settings.h" #include "StyleResolver.h" +#include "StyleScope.h" #include "TextResourceDecoder.h" #include "TextStream.h" +#include "TiledBacking.h" +#include "WheelEventTestTrigger.h" #include <wtf/CurrentTime.h> #include <wtf/Ref.h> -#include <wtf/TemporaryChange.h> - -#if USE(ACCELERATED_COMPOSITING) -#include "RenderLayerCompositor.h" -#include "TiledBacking.h" -#endif +#include <wtf/SetForScope.h> +#include <wtf/SystemTracing.h> -#if ENABLE(SVG) -#include "RenderSVGRoot.h" -#include "SVGDocument.h" -#include "SVGSVGElement.h" -#endif - -#if USE(TILED_BACKING_STORE) +#if USE(COORDINATED_GRAPHICS) #include "TiledBackingStore.h" #endif -#if ENABLE(TEXT_AUTOSIZING) -#include "TextAutosizer.h" +#if ENABLE(CSS_SCROLL_SNAP) +#include "AxisScrollSnapOffsets.h" #endif #if PLATFORM(IOS) #include "DocumentLoader.h" -#include "Logging.h" -#include "MemoryCache.h" -#include "MemoryPressureHandler.h" -#include "SystemMemory.h" -#include "TileCache.h" -#endif - -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) -#include "HTMLMediaElement.h" +#include "LegacyTileCache.h" #endif namespace WebCore { @@ -128,7 +131,7 @@ static RenderLayer::UpdateLayerPositionsFlags updateLayerPositionFlags(RenderLay flags &= ~RenderLayer::CheckForRepaint; flags |= RenderLayer::NeedsFullRepaintInBacking; } - if (isRelayoutingSubtree && layer->isPaginated()) + if (isRelayoutingSubtree && layer->enclosingPaginationLayer(RenderLayer::IncludeCompositedPaginatedLayers)) flags |= RenderLayer::UpdatePagination; return flags; } @@ -160,33 +163,107 @@ Pagination::Mode paginationModeForRenderStyle(const RenderStyle& style) return Pagination::BottomToTopPaginated; } +class SubtreeLayoutStateMaintainer { +public: + SubtreeLayoutStateMaintainer(RenderElement* subtreeLayoutRoot) + : m_layoutRoot(subtreeLayoutRoot) + { + if (m_layoutRoot) { + RenderView& view = m_layoutRoot->view(); + view.pushLayoutState(*m_layoutRoot); + if (shouldDisableLayoutStateForSubtree()) { + view.disableLayoutState(); + m_didDisableLayoutState = true; + } + } + } + + ~SubtreeLayoutStateMaintainer() + { + if (m_layoutRoot) { + RenderView& view = m_layoutRoot->view(); + view.popLayoutState(*m_layoutRoot); + if (m_didDisableLayoutState) + view.enableLayoutState(); + } + } + + bool shouldDisableLayoutStateForSubtree() + { + for (auto* renderer = m_layoutRoot; renderer; renderer = renderer->container()) { + if (renderer->hasTransform() || renderer->hasReflection()) + return true; + } + return false; + } + +private: + RenderElement* m_layoutRoot { nullptr }; + bool m_didDisableLayoutState { false }; +}; + +#ifndef NDEBUG +class RenderTreeNeedsLayoutChecker { +public : + RenderTreeNeedsLayoutChecker(const RenderElement& layoutRoot) + : m_layoutRoot(layoutRoot) + { + } + + ~RenderTreeNeedsLayoutChecker() + { + auto reportNeedsLayoutError = [] (const RenderObject& renderer) { + WTFReportError(__FILE__, __LINE__, WTF_PRETTY_FUNCTION, "post-layout: dirty renderer(s)"); + renderer.showRenderTreeForThis(); + ASSERT_NOT_REACHED(); + }; + + if (m_layoutRoot.needsLayout()) { + reportNeedsLayoutError(m_layoutRoot); + return; + } + + for (auto* descendant = m_layoutRoot.firstChild(); descendant; descendant = descendant->nextInPreOrder(&m_layoutRoot)) { + if (!descendant->needsLayout()) + continue; + + reportNeedsLayoutError(*descendant); + return; + } + } + +private: + const RenderElement& m_layoutRoot; +}; +#endif + FrameView::FrameView(Frame& frame) - : m_frame(&frame) + : m_frame(frame) , m_canHaveScrollbars(true) - , m_layoutTimer(this, &FrameView::layoutTimerFired) - , m_layoutRoot(0) + , m_layoutTimer(*this, &FrameView::layoutTimerFired) , m_layoutPhase(OutsideLayout) , m_inSynchronousPostLayout(false) - , m_postLayoutTasksTimer(this, &FrameView::postLayoutTimerFired) - , m_updateEmbeddedObjectsTimer(this, &FrameView::updateEmbeddedObjectsTimerFired) + , m_postLayoutTasksTimer(*this, &FrameView::performPostLayoutTasks) + , m_updateEmbeddedObjectsTimer(*this, &FrameView::updateEmbeddedObjectsTimerFired) , m_isTransparent(false) , m_baseBackgroundColor(Color::white) , m_mediaType("screen") , m_overflowStatusDirty(true) - , m_viewportRenderer(0) , m_wasScrolledByUser(false) , m_inProgrammaticScroll(false) , m_safeToPropagateScrollToParent(true) + , m_delayedScrollEventTimer(*this, &FrameView::sendScrollEvent) , m_isTrackingRepaints(false) , m_shouldUpdateWhileOffscreen(true) - , m_exposedRect(FloatRect::infiniteRect()) - , m_deferSetNeedsLayouts(0) + , m_deferSetNeedsLayoutCount(0) , m_setNeedsLayoutWasDeferred(false) , m_speculativeTilingEnabled(false) - , m_speculativeTilingEnableTimer(this, &FrameView::speculativeTilingEnableTimerFired) + , m_speculativeTilingEnableTimer(*this, &FrameView::speculativeTilingEnableTimerFired) #if PLATFORM(IOS) , m_useCustomFixedPositionLayoutRect(false) + , m_useCustomSizeForResizeEvent(false) #endif + , m_hasOverrideViewportSize(false) , m_shouldAutoSize(false) , m_inAutoSize(false) , m_didRunAutosize(false) @@ -195,29 +272,40 @@ FrameView::FrameView(Frame& frame) , m_footerHeight(0) , m_milestonesPendingPaint(0) , m_visualUpdatesAllowedByClient(true) + , m_hasFlippedBlockRenderers(false) , m_scrollPinningBehavior(DoNotPin) { init(); - if (frame.isMainFrame()) { - ScrollableArea::setVerticalScrollElasticity(ScrollElasticityAllowed); - ScrollableArea::setHorizontalScrollElasticity(ScrollElasticityAutomatic); +#if ENABLE(RUBBER_BANDING) + ScrollElasticity verticalElasticity = ScrollElasticityNone; + ScrollElasticity horizontalElasticity = ScrollElasticityNone; + if (m_frame->isMainFrame()) { + verticalElasticity = m_frame->page() ? m_frame->page()->verticalScrollElasticity() : ScrollElasticityAllowed; + horizontalElasticity = m_frame->page() ? m_frame->page()->horizontalScrollElasticity() : ScrollElasticityAllowed; + } else if (m_frame->settings().rubberBandingForSubScrollableRegionsEnabled()) { + verticalElasticity = ScrollElasticityAutomatic; + horizontalElasticity = ScrollElasticityAutomatic; } + + ScrollableArea::setVerticalScrollElasticity(verticalElasticity); + ScrollableArea::setHorizontalScrollElasticity(horizontalElasticity); +#endif } -PassRefPtr<FrameView> FrameView::create(Frame& frame) +Ref<FrameView> FrameView::create(Frame& frame) { - RefPtr<FrameView> view = adoptRef(new FrameView(frame)); + Ref<FrameView> view = adoptRef(*new FrameView(frame)); view->show(); - return view.release(); + return view; } -PassRefPtr<FrameView> FrameView::create(Frame& frame, const IntSize& initialSize) +Ref<FrameView> FrameView::create(Frame& frame, const IntSize& initialSize) { - RefPtr<FrameView> view = adoptRef(new FrameView(frame)); + Ref<FrameView> view = adoptRef(*new FrameView(frame)); view->Widget::setFrameRect(IntRect(view->location(), initialSize)); view->show(); - return view.release(); + return view; } FrameView::~FrameView() @@ -245,10 +333,8 @@ void FrameView::reset() m_cannotBlitToWindow = false; m_isOverlapped = false; m_contentIsOpaque = false; - m_borderX = 30; - m_borderY = 30; m_layoutTimer.stop(); - m_layoutRoot = 0; + m_layoutRoot = nullptr; m_delayedLayout = false; m_needsFullRepaint = true; m_layoutSchedulingEnabled = true; @@ -262,6 +348,7 @@ void FrameView::reset() m_firstLayoutCallbackPending = false; m_wasScrolledByUser = false; m_safeToPropagateScrollToParent = true; + m_delayedScrollEventTimer.stop(); m_lastViewportSize = IntSize(); m_lastZoomFactor = 1.0f; m_isTrackingRepaints = false; @@ -273,13 +360,17 @@ void FrameView::reset() m_visuallyNonEmptyPixelCount = 0; m_isVisuallyNonEmpty = false; m_firstVisuallyNonEmptyLayoutCallbackPending = true; - m_maintainScrollPositionAnchor = 0; + m_needsDeferredScrollbarsUpdate = false; + m_maintainScrollPositionAnchor = nullptr; } void FrameView::removeFromAXObjectCache() { - if (AXObjectCache* cache = axObjectCache()) + if (AXObjectCache* cache = axObjectCache()) { + if (HTMLFrameOwnerElement* owner = frame().ownerElement()) + cache->childrenChanged(owner->renderer()); cache->remove(this); + } } void FrameView::resetScrollbars() @@ -312,12 +403,12 @@ void FrameView::init() // Propagate the marginwidth/height and scrolling modes to the view. Element* ownerElement = frame().ownerElement(); - if (ownerElement && (ownerElement->hasTagName(frameTag) || ownerElement->hasTagName(iframeTag))) { - HTMLFrameElementBase* frameElt = toHTMLFrameElementBase(ownerElement); - if (frameElt->scrollingMode() == ScrollbarAlwaysOff) + if (is<HTMLFrameElementBase>(ownerElement)) { + HTMLFrameElementBase& frameElement = downcast<HTMLFrameElementBase>(*ownerElement); + if (frameElement.scrollingMode() == ScrollbarAlwaysOff) setCanHaveScrollbars(false); - LayoutUnit marginWidth = frameElt->marginWidth(); - LayoutUnit marginHeight = frameElt->marginHeight(); + LayoutUnit marginWidth = frameElement.marginWidth(); + LayoutUnit marginHeight = frameElement.marginHeight(); if (marginWidth != -1) setMarginWidth(marginWidth); if (marginHeight != -1) @@ -338,7 +429,7 @@ void FrameView::prepareForDetach() if (frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = frame().page()->scrollingCoordinator()) - scrollingCoordinator->willDestroyScrollableArea(this); + scrollingCoordinator->willDestroyScrollableArea(*this); } } @@ -358,7 +449,14 @@ void FrameView::detachCustomScrollbars() void FrameView::recalculateScrollbarOverlayStyle() { ScrollbarOverlayStyle oldOverlayStyle = scrollbarOverlayStyle(); - ScrollbarOverlayStyle overlayStyle = ScrollbarOverlayStyleDefault; + std::optional<ScrollbarOverlayStyle> clientOverlayStyle = frame().page() ? frame().page()->chrome().client().preferredScrollbarOverlayStyle() : ScrollbarOverlayStyleDefault; + if (clientOverlayStyle) { + if (clientOverlayStyle.value() != oldOverlayStyle) + setScrollbarOverlayStyle(clientOverlayStyle.value()); + return; + } + + ScrollbarOverlayStyle computedOverlayStyle = ScrollbarOverlayStyleDefault; Color backgroundColor = documentBackgroundColor(); if (backgroundColor.isValid()) { @@ -367,12 +465,12 @@ void FrameView::recalculateScrollbarOverlayStyle() // heuristic. double hue, saturation, lightness; backgroundColor.getHSL(hue, saturation, lightness); - if (lightness <= .5 && backgroundColor.alpha() > 0) - overlayStyle = ScrollbarOverlayStyleLight; + if (lightness <= .5 && backgroundColor.isVisible()) + computedOverlayStyle = ScrollbarOverlayStyleLight; } - if (oldOverlayStyle != overlayStyle) - setScrollbarOverlayStyle(overlayStyle); + if (oldOverlayStyle != computedOverlayStyle) + setScrollbarOverlayStyle(computedOverlayStyle); } void FrameView::clear() @@ -386,11 +484,20 @@ void FrameView::clear() #if PLATFORM(IOS) // To avoid flashes of white, disable tile updates immediately when view is cleared at the beginning of a page load. // Tiling will be re-enabled from UIKit via [WAKWindow setTilingMode:] when we have content to draw. - if (TileCache* tileCache = this->tileCache()) - tileCache->setTilingMode(TileCache::Disabled); + if (LegacyTileCache* tileCache = legacyTileCache()) + tileCache->setTilingMode(LegacyTileCache::Disabled); #endif } +#if PLATFORM(IOS) +void FrameView::didReplaceMultipartContent() +{ + // Re-enable tile updates that were disabled in clear(). + if (LegacyTileCache* tileCache = legacyTileCache()) + tileCache->setTilingMode(LegacyTileCache::Normal); +} +#endif + bool FrameView::didFirstLayout() const { return !m_firstLayout; @@ -399,63 +506,50 @@ bool FrameView::didFirstLayout() const void FrameView::invalidateRect(const IntRect& rect) { if (!parent()) { - if (HostWindow* window = hostWindow()) - window->invalidateContentsAndRootView(rect, false /*immediate*/); + if (auto* page = frame().page()) + page->chrome().invalidateContentsAndRootView(rect); return; } - RenderWidget* renderer = frame().ownerRenderer(); + auto* renderer = frame().ownerRenderer(); if (!renderer) return; IntRect repaintRect = rect; - repaintRect.move(renderer->borderLeft() + renderer->paddingLeft(), - renderer->borderTop() + renderer->paddingTop()); + repaintRect.move(renderer->borderLeft() + renderer->paddingLeft(), renderer->borderTop() + renderer->paddingTop()); renderer->repaintRectangle(repaintRect); } void FrameView::setFrameRect(const IntRect& newRect) { + Ref<FrameView> protectedThis(*this); IntRect oldRect = frameRect(); if (newRect == oldRect) return; -#if ENABLE(TEXT_AUTOSIZING) - // Autosized font sizes depend on the width of the viewing area. - if (newRect.width() != oldRect.width()) { - Page* page = frame().page(); - if (frame().isMainFrame() && page->settings().textAutosizingEnabled()) { - for (Frame* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) - frame().document()->textAutosizer()->recalculateMultipliers(); - } - } -#endif - ScrollView::setFrameRect(newRect); updateScrollableAreaSet(); -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { if (renderView->usesCompositing()) renderView->compositor().frameViewDidChangeSize(); } -#endif - if (!frameFlatteningEnabled()) - sendResizeEventIfNeeded(); + if (frame().isMainFrame()) + frame().mainFrame().pageOverlayController().didChangeViewSize(); + + viewportContentsChanged(); } -#if ENABLE(REQUEST_ANIMATION_FRAME) bool FrameView::scheduleAnimation() { - if (HostWindow* window = hostWindow()) { - window->scheduleAnimation(); - return true; - } - return false; + auto* page = frame().page(); + if (!page) + return false; + page->chrome().scheduleAnimation(); + return true; } -#endif void FrameView::setMarginWidth(LayoutUnit w) { @@ -512,42 +606,67 @@ void FrameView::updateCanHaveScrollbars() setCanHaveScrollbars(true); } -PassRefPtr<Scrollbar> FrameView::createScrollbar(ScrollbarOrientation orientation) +Ref<Scrollbar> FrameView::createScrollbar(ScrollbarOrientation orientation) { - if (!frame().settings().allowCustomScrollbarInMainFrame() && frame().isMainFrame()) - return ScrollView::createScrollbar(orientation); - // FIXME: We need to update the scrollbar dynamically as documents change (or as doc elements and bodies get discovered that have custom styles). Document* doc = frame().document(); // Try the <body> element first as a scrollbar source. - Element* body = doc ? doc->body() : 0; + HTMLElement* body = doc ? doc->bodyOrFrameset() : nullptr; if (body && body->renderer() && body->renderer()->style().hasPseudoStyle(SCROLLBAR)) - return RenderScrollbar::createCustomScrollbar(this, orientation, body); + return RenderScrollbar::createCustomScrollbar(*this, orientation, body); // If the <body> didn't have a custom style, then the root element might. - Element* docElement = doc ? doc->documentElement() : 0; + Element* docElement = doc ? doc->documentElement() : nullptr; if (docElement && docElement->renderer() && docElement->renderer()->style().hasPseudoStyle(SCROLLBAR)) - return RenderScrollbar::createCustomScrollbar(this, orientation, docElement); + return RenderScrollbar::createCustomScrollbar(*this, orientation, docElement); // If we have an owning iframe/frame element, then it can set the custom scrollbar also. RenderWidget* frameRenderer = frame().ownerRenderer(); if (frameRenderer && frameRenderer->style().hasPseudoStyle(SCROLLBAR)) - return RenderScrollbar::createCustomScrollbar(this, orientation, 0, &frame()); + return RenderScrollbar::createCustomScrollbar(*this, orientation, nullptr, &frame()); // Nobody set a custom style, so we just use a native scrollbar. return ScrollView::createScrollbar(orientation); } +void FrameView::didRestoreFromPageCache() +{ + // When restoring from page cache, the main frame stays in place while subframes get swapped in. + // We update the scrollable area set to ensure that scrolling data structures get invalidated. + updateScrollableAreaSet(); +} + +void FrameView::willDestroyRenderTree() +{ + detachCustomScrollbars(); + m_layoutRoot = nullptr; +} + +void FrameView::didDestroyRenderTree() +{ + ASSERT(!m_layoutRoot); + ASSERT(m_widgetsInRenderTree.isEmpty()); + + // If the render tree is destroyed below FrameView::updateEmbeddedObjects(), there will still be a null sentinel in the set. + // Everything else should have removed itself as the tree was felled. + ASSERT(!m_embeddedObjectsToUpdate || m_embeddedObjectsToUpdate->isEmpty() || (m_embeddedObjectsToUpdate->size() == 1 && m_embeddedObjectsToUpdate->first() == nullptr)); + + ASSERT(!m_viewportConstrainedObjects || m_viewportConstrainedObjects->isEmpty()); + ASSERT(!m_slowRepaintObjects || m_slowRepaintObjects->isEmpty()); + + ASSERT(!frame().animation().hasAnimations()); +} + void FrameView::setContentsSize(const IntSize& size) { if (size == contentsSize()) return; - m_deferSetNeedsLayouts++; + m_deferSetNeedsLayoutCount++; ScrollView::setContentsSize(size); - ScrollView::contentsResized(); + contentsResized(); Page* page = frame().page(); if (!page) @@ -555,12 +674,17 @@ void FrameView::setContentsSize(const IntSize& size) updateScrollableAreaSet(); - page->chrome().contentsSizeChanged(&frame(), size); // Notify only. + page->chrome().contentsSizeChanged(frame(), size); // Notify only. - ASSERT(m_deferSetNeedsLayouts); - m_deferSetNeedsLayouts--; + if (frame().isMainFrame()) { + frame().mainFrame().pageOverlayController().didChangeDocumentSize(); + PageCache::singleton().markPagesForContentsSizeChanged(*page); + } + + ASSERT(m_deferSetNeedsLayoutCount); + m_deferSetNeedsLayoutCount--; - if (!m_deferSetNeedsLayouts) + if (!m_deferSetNeedsLayoutCount) m_setNeedsLayoutWasDeferred = false; // FIXME: Find a way to make the deferred layout actually happen. } @@ -575,11 +699,13 @@ void FrameView::adjustViewSize() const IntRect rect = renderView->documentRect(); const IntSize& size = rect.size(); ScrollView::setScrollOrigin(IntPoint(-rect.x(), -rect.y()), !frame().document()->printing(), size == contentsSize()); - + + LOG_WITH_STREAM(Layout, stream << "FrameView " << this << " adjustViewSize: unscaled document rect changed to " << renderView->unscaledDocumentRect() << " (scaled to " << size << ")"); + setContentsSize(size); } -void FrameView::applyOverflowToViewport(RenderElement* o, ScrollbarMode& hMode, ScrollbarMode& vMode) +void FrameView::applyOverflowToViewport(const RenderElement& renderer, ScrollbarMode& hMode, ScrollbarMode& vMode) { // Handle the overflow:hidden/scroll case for the body/html elements. WinIE treats // overflow:hidden and overflow:scroll on <body> as applying to the document's @@ -592,18 +718,17 @@ void FrameView::applyOverflowToViewport(RenderElement* o, ScrollbarMode& hMode, bool overrideHidden = frame().isMainFrame() && ((frame().frameScaleFactor() > 1) || headerHeight() || footerHeight()); - EOverflow overflowX = o->style().overflowX(); - EOverflow overflowY = o->style().overflowY(); + EOverflow overflowX = renderer.style().overflowX(); + EOverflow overflowY = renderer.style().overflowY(); -#if ENABLE(SVG) - if (o->isSVGRoot()) { - // overflow is ignored in stand-alone SVG documents. - if (!toRenderSVGRoot(o)->isEmbeddedThroughFrameContainingSVGDocument()) - return; - overflowX = OHIDDEN; - overflowY = OHIDDEN; + if (is<RenderSVGRoot>(renderer)) { + // FIXME: evaluate if we can allow overflow for these cases too. + // Overflow is always hidden when stand-alone SVG documents are embedded. + if (downcast<RenderSVGRoot>(renderer).isEmbeddedThroughFrameContainingSVGDocument()) { + overflowX = OHIDDEN; + overflowY = OHIDDEN; + } } -#endif switch (overflowX) { case OHIDDEN: @@ -640,41 +765,38 @@ void FrameView::applyOverflowToViewport(RenderElement* o, ScrollbarMode& hMode, // Don't set it at all. Values of OPAGEDX and OPAGEDY are handled by applyPaginationToViewPort(). ; } - - m_viewportRenderer = o; } void FrameView::applyPaginationToViewport() { - Document* document = frame().document(); - auto documentElement = document->documentElement(); - RenderElement* documentRenderer = documentElement ? documentElement->renderer() : nullptr; - RenderElement* documentOrBodyRenderer = documentRenderer; - auto body = document->body(); - if (body && body->renderer()) { - if (body->hasTagName(bodyTag)) - documentOrBodyRenderer = documentRenderer->style().overflowX() == OVISIBLE && documentElement->hasTagName(htmlTag) ? body->renderer() : documentRenderer; + auto* document = frame().document(); + auto* documentElement = document ? document->documentElement() : nullptr; + if (!documentElement || !documentElement->renderer()) { + setPagination(Pagination()); + return; } - Pagination pagination; + auto& documentRenderer = *documentElement->renderer(); + auto* documentOrBodyRenderer = &documentRenderer; - if (!documentOrBodyRenderer) { - setPagination(pagination); - return; + auto* body = document->body(); + if (body && body->renderer()) { + documentOrBodyRenderer = documentRenderer.style().overflowX() == OVISIBLE && is<HTMLHtmlElement>(*documentElement) ? + body->renderer() : &documentRenderer; } + Pagination pagination; EOverflow overflowY = documentOrBodyRenderer->style().overflowY(); if (overflowY == OPAGEDX || overflowY == OPAGEDY) { pagination.mode = WebCore::paginationModeForRenderStyle(documentOrBodyRenderer->style()); pagination.gap = static_cast<unsigned>(documentOrBodyRenderer->style().columnGap()); } - setPagination(pagination); } void FrameView::calculateScrollbarModesForLayout(ScrollbarMode& hMode, ScrollbarMode& vMode, ScrollbarModesCalculationStrategy strategy) { - m_viewportRenderer = 0; + m_viewportRendererType = ViewportRendererType::None; const HTMLFrameOwnerElement* owner = frame().ownerElement(); if (owner && (owner->scrollingMode() == ScrollbarAlwaysOff)) { @@ -685,51 +807,75 @@ void FrameView::calculateScrollbarModesForLayout(ScrollbarMode& hMode, Scrollbar if (m_canHaveScrollbars || strategy == RulesFromWebContentOnly) { hMode = ScrollbarAuto; - // Seamless documents begin with heights of 0; we special case that here - // to correctly render documents that don't need scrollbars. - IntSize fullVisibleSize = visibleContentRectIncludingScrollbars(LegacyIOSDocumentVisibleRect).size(); - bool isSeamlessDocument = frame().document() && frame().document()->shouldDisplaySeamlesslyWithParent(); - vMode = (isSeamlessDocument && !fullVisibleSize.height()) ? ScrollbarAlwaysOff : ScrollbarAuto; + vMode = ScrollbarAuto; } else { hMode = ScrollbarAlwaysOff; vMode = ScrollbarAlwaysOff; } - if (!m_layoutRoot) { - Document* document = frame().document(); - auto documentElement = document->documentElement(); - RenderElement* rootRenderer = documentElement ? documentElement->renderer() : nullptr; - auto body = document->body(); - if (body && body->renderer()) { - if (body->hasTagName(framesetTag) && !frameFlatteningEnabled()) { - vMode = ScrollbarAlwaysOff; - hMode = ScrollbarAlwaysOff; - } else if (body->hasTagName(bodyTag)) { - // It's sufficient to just check the X overflow, - // since it's illegal to have visible in only one direction. - RenderElement* o = rootRenderer->style().overflowX() == OVISIBLE && document->documentElement()->hasTagName(htmlTag) ? body->renderer() : rootRenderer; - applyOverflowToViewport(o, hMode, vMode); + if (m_layoutRoot) + return; + + auto* document = frame().document(); + if (!document) + return; + + auto* documentElement = document->documentElement(); + if (!documentElement) + return; + + auto* bodyOrFrameset = document->bodyOrFrameset(); + auto* rootRenderer = documentElement->renderer(); + if (!bodyOrFrameset || !bodyOrFrameset->renderer()) { + if (rootRenderer) { + applyOverflowToViewport(*rootRenderer, hMode, vMode); + m_viewportRendererType = ViewportRendererType::Document; + } + return; + } + + if (is<HTMLFrameSetElement>(*bodyOrFrameset) && !frameFlatteningEnabled()) { + vMode = ScrollbarAlwaysOff; + hMode = ScrollbarAlwaysOff; + return; + } + + if (is<HTMLBodyElement>(*bodyOrFrameset) && rootRenderer) { + // It's sufficient to just check the X overflow, + // since it's illegal to have visible in only one direction. + if (rootRenderer->style().overflowX() == OVISIBLE && is<HTMLHtmlElement>(documentElement)) { + auto* bodyRenderer = bodyOrFrameset->renderer(); + if (bodyRenderer) { + applyOverflowToViewport(*bodyRenderer, hMode, vMode); + m_viewportRendererType = ViewportRendererType::Body; } - } else if (rootRenderer) - applyOverflowToViewport(rootRenderer, hMode, vMode); - } + } else { + applyOverflowToViewport(*rootRenderer, hMode, vMode); + m_viewportRendererType = ViewportRendererType::Document; + } + } } -#if USE(ACCELERATED_COMPOSITING) -void FrameView::updateCompositingLayersAfterStyleChange() +void FrameView::willRecalcStyle() { RenderView* renderView = this->renderView(); if (!renderView) return; + renderView->compositor().willRecalcStyle(); +} + +bool FrameView::updateCompositingLayersAfterStyleChange() +{ + RenderView* renderView = this->renderView(); + if (!renderView) + return false; + // If we expect to update compositing after an incipient layout, don't do so here. if (inPreLayoutStyleUpdate() || layoutPending() || renderView->needsLayout()) - return; + return false; - RenderLayerCompositor& compositor = renderView->compositor(); - // This call will make sure the cached hasAcceleratedCompositing is updated from the pref - compositor.cacheAcceleratedCompositingFlags(); - compositor.updateCompositingLayers(CompositingUpdateAfterStyleChange); + return renderView->compositor().didRecalcStyleWithNoPendingLayout(); } void FrameView::updateCompositingLayersAfterLayout() @@ -755,32 +901,11 @@ void FrameView::clearBackingStores() compositor.clearBackingForAllLayers(); } -void FrameView::restoreBackingStores() -{ - RenderView* renderView = this->renderView(); - if (!renderView) - return; - - RenderLayerCompositor& compositor = renderView->compositor(); - compositor.enableCompositingMode(true); - compositor.updateCompositingLayers(CompositingUpdateAfterLayout); -} - -bool FrameView::usesCompositedScrolling() const -{ - RenderView* renderView = this->renderView(); - if (!renderView) - return false; - if (frame().settings().compositedScrollingForFramesEnabled()) - return renderView->compositor().inForcedCompositingMode(); - return false; -} - GraphicsLayer* FrameView::layerForScrolling() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().scrollLayer(); } @@ -788,7 +913,7 @@ GraphicsLayer* FrameView::layerForHorizontalScrollbar() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().layerForHorizontalScrollbar(); } @@ -796,7 +921,7 @@ GraphicsLayer* FrameView::layerForVerticalScrollbar() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().layerForVerticalScrollbar(); } @@ -804,7 +929,7 @@ GraphicsLayer* FrameView::layerForScrollCorner() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().layerForScrollCorner(); } @@ -812,11 +937,11 @@ TiledBacking* FrameView::tiledBacking() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; RenderLayerBacking* backing = renderView->layer()->backing(); if (!backing) - return 0; + return nullptr; return backing->graphicsLayer()->tiledBacking(); } @@ -831,7 +956,16 @@ uint64_t FrameView::scrollLayerID() const if (!backing) return 0; - return backing->scrollLayerID(); + return backing->scrollingNodeIDForRole(Scrolling); +} + +ScrollableArea* FrameView::scrollableAreaForScrollLayerID(uint64_t nodeID) const +{ + RenderView* renderView = this->renderView(); + if (!renderView) + return nullptr; + + return renderView->compositor().scrollableAreaForScrollLayerID(nodeID); } #if ENABLE(RUBBER_BANDING) @@ -839,7 +973,7 @@ GraphicsLayer* FrameView::layerForOverhangAreas() const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().layerForOverhangAreas(); } @@ -847,7 +981,7 @@ GraphicsLayer* FrameView::setWantsLayerForTopOverHangArea(bool wantsLayer) const { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().updateLayerForTopOverhangArea(wantsLayer); } @@ -856,14 +990,56 @@ GraphicsLayer* FrameView::setWantsLayerForBottomOverHangArea(bool wantsLayer) co { RenderView* renderView = this->renderView(); if (!renderView) - return 0; + return nullptr; return renderView->compositor().updateLayerForBottomOverhangArea(wantsLayer); } #endif // ENABLE(RUBBER_BANDING) -bool FrameView::flushCompositingStateForThisFrame(Frame* rootFrameForFlush) +#if ENABLE(CSS_SCROLL_SNAP) +void FrameView::updateSnapOffsets() +{ + if (!frame().document()) + return; + + // FIXME: Should we allow specifying snap points through <html> tags too? + HTMLElement* body = frame().document()->bodyOrFrameset(); + if (!renderView() || !body || !body->renderer()) + return; + + updateSnapOffsetsForScrollableArea(*this, *body, *renderView(), body->renderer()->style()); +} + +bool FrameView::isScrollSnapInProgress() const +{ + if (scrollbarsSuppressed()) + return false; + + // If the scrolling thread updates the scroll position for this FrameView, then we should return + // ScrollingCoordinator::isScrollSnapInProgress(). + if (Page* page = frame().page()) { + if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) { + if (!scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously(*this)) + return scrollingCoordinator->isScrollSnapInProgress(); + } + } + + // If the main thread updates the scroll position for this FrameView, we should return + // ScrollAnimator::isScrollSnapInProgress(). + if (ScrollAnimator* scrollAnimator = existingScrollAnimator()) + return scrollAnimator->isScrollSnapInProgress(); + + return false; +} + +void FrameView::updateScrollingCoordinatorScrollSnapProperties() const +{ + renderView()->compositor().updateScrollSnapPropertiesWithFrameView(*this); +} +#endif + +bool FrameView::flushCompositingStateForThisFrame(const Frame& rootFrameForFlush) { RenderView* renderView = this->renderView(); if (!renderView) @@ -877,12 +1053,11 @@ bool FrameView::flushCompositingStateForThisFrame(Frame* rootFrameForFlush) return false; #if PLATFORM(IOS) - if (TileCache* tileCache = this->tileCache()) + if (LegacyTileCache* tileCache = legacyTileCache()) tileCache->doPendingRepaints(); #endif - renderView->compositor().flushPendingLayerChanges(rootFrameForFlush == &frame()); - + renderView->compositor().flushPendingLayerChanges(&rootFrameForFlush == m_frame.ptr()); return true; } @@ -896,22 +1071,22 @@ GraphicsLayer* FrameView::graphicsLayerForPlatformWidget(PlatformWidget platform { // To find the Widget that corresponds with platformWidget we have to do a linear // search of our child widgets. - Widget* foundWidget = nullptr; + const Widget* foundWidget = nullptr; for (auto& widget : children()) { if (widget->platformWidget() != platformWidget) continue; - foundWidget = widget.get(); + foundWidget = widget.ptr(); break; } if (!foundWidget) return nullptr; - auto* renderWidget = RenderWidget::find(foundWidget); + auto* renderWidget = RenderWidget::find(*foundWidget); if (!renderWidget) return nullptr; - RenderLayer* widgetLayer = renderWidget->layer(); + auto* widgetLayer = renderWidget->layer(); if (!widgetLayer || !widgetLayer->isComposited()) return nullptr; @@ -925,7 +1100,36 @@ void FrameView::scheduleLayerFlushAllowingThrottling() return; view->compositor().scheduleLayerFlush(true /* canThrottle */); } -#endif // USE(ACCELERATED_COMPOSITING) + +LayoutRect FrameView::fixedScrollableAreaBoundsInflatedForScrolling(const LayoutRect& uninflatedBounds) const +{ + LayoutPoint scrollPosition; + LayoutSize topLeftExpansion; + LayoutSize bottomRightExpansion; + + if (frame().settings().visualViewportEnabled()) { + // FIXME: this is wrong under zooming; uninflatedBounds is scaled but the scroll positions are not. + scrollPosition = layoutViewportRect().location(); + topLeftExpansion = scrollPosition - unscaledMinimumScrollPosition(); + bottomRightExpansion = unscaledMaximumScrollPosition() - scrollPosition; + } else { + scrollPosition = scrollPositionRespectingCustomFixedPosition(); + topLeftExpansion = scrollPosition - minimumScrollPosition(); + bottomRightExpansion = maximumScrollPosition() - scrollPosition; + } + + return LayoutRect(uninflatedBounds.location() - topLeftExpansion, uninflatedBounds.size() + topLeftExpansion + bottomRightExpansion); +} + +LayoutPoint FrameView::scrollPositionRespectingCustomFixedPosition() const +{ +#if PLATFORM(IOS) + if (!frame().settings().visualViewportEnabled()) + return useCustomFixedPositionLayoutRect() ? customFixedPositionLayoutRect().location() : scrollPosition(); +#endif + + return scrollPositionForFixedPosition(); +} void FrameView::setHeaderHeight(int headerHeight) { @@ -947,103 +1151,101 @@ void FrameView::setFooterHeight(int footerHeight) renderView->setNeedsLayout(); } -bool FrameView::hasCompositedContent() const +float FrameView::topContentInset(TopContentInsetType contentInsetTypeToReturn) const { -#if USE(ACCELERATED_COMPOSITING) - if (RenderView* renderView = this->renderView()) - return renderView->compositor().inCompositingMode(); -#endif - return false; -} + if (platformWidget() && contentInsetTypeToReturn == TopContentInsetType::WebCoreOrPlatformContentInset) + return platformTopContentInset(); -bool FrameView::hasCompositedContentIncludingDescendants() const + if (!frame().isMainFrame()) + return 0; + + Page* page = frame().page(); + return page ? page->topContentInset() : 0; +} + +void FrameView::topContentInsetDidChange(float newTopContentInset) { -#if USE(ACCELERATED_COMPOSITING) - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { - RenderView* renderView = frame->contentRenderer(); - if (RenderLayerCompositor* compositor = renderView ? &renderView->compositor() : 0) { - if (compositor->inCompositingMode()) - return true; + RenderView* renderView = this->renderView(); + if (!renderView) + return; - if (!RenderLayerCompositor::allowsIndependentlyCompositedFrames(this)) - break; - } - } -#endif - return false; + if (platformWidget()) + platformSetTopContentInset(newTopContentInset); + + layout(); + + updateScrollbars(scrollPosition()); + if (renderView->usesCompositing()) + renderView->compositor().frameViewDidChangeSize(); + + if (TiledBacking* tiledBacking = this->tiledBacking()) + tiledBacking->setTopContentInset(newTopContentInset); } -bool FrameView::hasCompositingAncestor() const +void FrameView::topContentDirectionDidChange() { -#if USE(ACCELERATED_COMPOSITING) - for (Frame* frame = this->frame().tree().parent(); frame; frame = frame->tree().parent()) { - if (FrameView* view = frame->view()) { - if (view->hasCompositedContent()) - return true; - } - } -#endif + m_needsDeferredScrollbarsUpdate = true; +} + +void FrameView::handleDeferredScrollbarsUpdateAfterDirectionChange() +{ + if (!m_needsDeferredScrollbarsUpdate) + return; + + m_needsDeferredScrollbarsUpdate = false; + + ASSERT(m_layoutPhase == InPostLayerPositionsUpdatedAfterLayout); + updateScrollbars(scrollPosition()); + positionScrollbarLayers(); +} + +bool FrameView::hasCompositedContent() const +{ + if (RenderView* renderView = this->renderView()) + return renderView->compositor().inCompositingMode(); return false; } // Sometimes (for plug-ins) we need to eagerly go into compositing mode. void FrameView::enterCompositingMode() { -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { renderView->compositor().enableCompositingMode(); if (!needsLayout()) renderView->compositor().scheduleCompositingLayerUpdate(); } -#endif } bool FrameView::isEnclosedInCompositingLayer() const { -#if USE(ACCELERATED_COMPOSITING) auto frameOwnerRenderer = frame().ownerRenderer(); if (frameOwnerRenderer && frameOwnerRenderer->containerForRepaint()) return true; if (FrameView* parentView = parentFrameView()) return parentView->isEnclosedInCompositingLayer(); -#endif return false; } bool FrameView::flushCompositingStateIncludingSubframes() { -#if USE(ACCELERATED_COMPOSITING) - bool allFramesFlushed = flushCompositingStateForThisFrame(&frame()); - - for (Frame* child = frame().tree().firstChild(); child; child = child->tree().traverseNext(&frame())) { - bool flushed = child->view()->flushCompositingStateForThisFrame(&frame()); + InspectorInstrumentation::willComposite(frame()); + + bool allFramesFlushed = flushCompositingStateForThisFrame(frame()); + + for (Frame* child = frame().tree().firstRenderedChild(); child; child = child->tree().traverseNextRendered(m_frame.ptr())) { + if (!child->view()) + continue; + bool flushed = child->view()->flushCompositingStateForThisFrame(frame()); allFramesFlushed &= flushed; } return allFramesFlushed; -#else // USE(ACCELERATED_COMPOSITING) - return true; -#endif } bool FrameView::isSoftwareRenderable() const { -#if USE(ACCELERATED_COMPOSITING) RenderView* renderView = this->renderView(); return !renderView || !renderView->compositor().has3DContent(); -#else - return true; -#endif -} - -void FrameView::didMoveOnscreen() -{ - contentAreaDidShow(); -} - -void FrameView::willMoveOffscreen() -{ - contentAreaDidHide(); } void FrameView::setIsInWindow(bool isInWindow) @@ -1052,14 +1254,8 @@ void FrameView::setIsInWindow(bool isInWindow) renderView->setIsInWindow(isInWindow); } -RenderObject* FrameView::layoutRoot(bool onlyDuringLayout) const -{ - return onlyDuringLayout && layoutPending() ? 0 : m_layoutRoot; -} - inline void FrameView::forceLayoutParentViewIfNeeded() { -#if ENABLE(SVG) RenderWidget* ownerRenderer = frame().ownerRenderer(); if (!ownerRenderer) return; @@ -1068,50 +1264,59 @@ inline void FrameView::forceLayoutParentViewIfNeeded() if (!contentBox) return; - RenderSVGRoot* svgRoot = toRenderSVGRoot(contentBox); - if (svgRoot->everHadLayout() && !svgRoot->needsLayout()) + auto& svgRoot = downcast<RenderSVGRoot>(*contentBox); + if (svgRoot.everHadLayout() && !svgRoot.needsLayout()) return; + LOG(Layout, "FrameView %p forceLayoutParentViewIfNeeded scheduling layout on parent FrameView %p", this, &ownerRenderer->view().frameView()); + // If the embedded SVG document appears the first time, the ownerRenderer has already finished // layout without knowing about the existence of the embedded SVG document, because RenderReplaced - // embeddedContentBox() returns 0, as long as the embedded document isn't loaded yet. Before + // embeddedContentBox() returns nullptr, as long as the embedded document isn't loaded yet. Before // bothering to lay out the SVG document, mark the ownerRenderer needing layout and ask its // FrameView for a layout. After that the RenderEmbeddedObject (ownerRenderer) carries the // correct size, which RenderSVGRoot::computeReplacedLogicalWidth/Height rely on, when laying // out for the first time, or when the RenderSVGRoot size has changed dynamically (eg. via <script>). - Ref<FrameView> frameView(ownerRenderer->view().frameView()); - // Mark the owner renderer as needing layout. ownerRenderer->setNeedsLayoutAndPrefWidthsRecalc(); - - // Synchronously enter layout, to layout the view containing the host object/embed/iframe. - frameView->layout(); -#endif + ownerRenderer->view().frameView().scheduleRelayout(); } void FrameView::layout(bool allowSubtree) { - if (isInLayout()) + ASSERT_WITH_SECURITY_IMPLICATION(!frame().document()->inRenderTreeUpdate()); + + LOG(Layout, "FrameView %p (%dx%d) layout, main frameview %d, allowSubtree=%d", this, size().width(), size().height(), frame().isMainFrame(), allowSubtree); + if (isInRenderTreeLayout()) { + LOG(Layout, " in layout, bailing"); + return; + } + + if (layoutDisallowed()) { + LOG(Layout, " layout is disallowed, bailing"); return; + } + + // Protect the view from being deleted during layout (in recalcStyle). + Ref<FrameView> protectedThis(*this); // Many of the tasks performed during layout can cause this function to be re-entered, // so save the layout phase now and restore it on exit. - TemporaryChange<LayoutPhase> layoutPhaseRestorer(m_layoutPhase, InPreLayout); - - // Protect the view from being deleted during layout (in recalcStyle) - Ref<FrameView> protect(*this); + SetForScope<LayoutPhase> layoutPhaseRestorer(m_layoutPhase, InPreLayout); // Every scroll that happens during layout is programmatic. - TemporaryChange<bool> changeInProgrammaticScroll(m_inProgrammaticScroll, true); + SetForScope<bool> changeInProgrammaticScroll(m_inProgrammaticScroll, true); bool inChildFrameLayoutWithFrameFlattening = isInChildFrameWithFrameFlattening(); if (inChildFrameLayoutWithFrameFlattening) { startLayoutAtMainFrameViewIfNeeded(allowSubtree); RenderElement* root = m_layoutRoot ? m_layoutRoot : frame().document()->renderView(); - if (!root->needsLayout()) + if (!root || !root->needsLayout()) return; } + + TraceScope tracingScope(LayoutStart, LayoutEnd); #if PLATFORM(IOS) if (updateFixedPositionLayoutRect()) @@ -1127,40 +1332,37 @@ void FrameView::layout(bool allowSubtree) if (isPainting()) return; - InspectorInstrumentationCookie cookie = InspectorInstrumentation::willLayout(&frame()); - - if (!allowSubtree && m_layoutRoot) { - m_layoutRoot->markContainingBlocksForLayout(false); - m_layoutRoot = 0; - } + InspectorInstrumentationCookie cookie = InspectorInstrumentation::willLayout(frame()); + AnimationUpdateBlock animationUpdateBlock(&frame().animation()); + + if (!allowSubtree && m_layoutRoot) + convertSubtreeLayoutToFullLayout(); ASSERT(frame().view() == this); ASSERT(frame().document()); Document& document = *frame().document(); - ASSERT(!document.inPageCache()); - - bool subtree; - RenderElement* root; + ASSERT(document.pageCacheState() == Document::NotInPageCache); { - TemporaryChange<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false); + SetForScope<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false); if (!m_nestedLayoutCount && !m_inSynchronousPostLayout && m_postLayoutTasksTimer.isActive() && !inChildFrameLayoutWithFrameFlattening) { // This is a new top-level layout. If there are any remaining tasks from the previous // layout, finish them now. - TemporaryChange<bool> inSynchronousPostLayoutChange(m_inSynchronousPostLayout, true); + SetForScope<bool> inSynchronousPostLayoutChange(m_inSynchronousPostLayout, true); performPostLayoutTasks(); } m_layoutPhase = InPreLayoutStyleUpdate; // Viewport-dependent media queries may cause us to need completely different style information. - StyleResolver* styleResolver = document.styleResolverIfExists(); - if (!styleResolver || styleResolver->affectedByViewportChange()) { - document.styleResolverChanged(DeferRecalcStyle); + auto* styleResolver = document.styleScope().resolverIfExists(); + if (!styleResolver || styleResolver->hasMediaQueriesAffectedByViewportChange()) { + LOG(Layout, " hasMediaQueriesAffectedByViewportChange, enqueueing style recalc"); + document.styleScope().didChangeStyleSheetEnvironment(); // FIXME: This instrumentation event is not strictly accurate since cached media query results do not persist across StyleResolver rebuilds. - InspectorInstrumentation::mediaQueryResultChanged(&document); + InspectorInstrumentation::mediaQueryResultChanged(document); } else document.evaluateMediaQueryList(); @@ -1171,54 +1373,52 @@ void FrameView::layout(bool allowSubtree) // Always ensure our style info is up-to-date. This can happen in situations where // the layout beats any sort of style recalc update that needs to occur. document.updateStyleIfNeeded(); - m_layoutPhase = InPreLayout; - - subtree = m_layoutRoot; - - // If there is only one ref to this view left, then its going to be destroyed as soon as we exit, + // If there is only one ref to this view left, then its going to be destroyed as soon as we exit, // so there's no point to continuing to layout if (hasOneRef()) return; - root = subtree ? m_layoutRoot : document.renderView(); - if (!root) { - // FIXME: Do we need to set m_size here? - return; - } - // Close block here so we can set up the font cache purge preventer, which we will still // want in scope even after we want m_layoutSchedulingEnabled to be restored again. // The next block sets m_layoutSchedulingEnabled back to false once again. } - FontCachePurgePreventer fontCachePurgePreventer; - RenderLayer* layer; + m_layoutPhase = InPreLayout; + + RenderLayer* layer = nullptr; + bool subtree = false; + RenderElement* root = nullptr; ++m_nestedLayoutCount; { - TemporaryChange<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false); + SetForScope<bool> changeSchedulingEnabled(m_layoutSchedulingEnabled, false); + + autoSizeIfEnabled(); + + root = m_layoutRoot ? m_layoutRoot : document.renderView(); + if (!root) + return; + subtree = m_layoutRoot; if (!m_layoutRoot) { - HTMLElement* body = document.body(); + auto* body = document.bodyOrFrameset(); if (body && body->renderer()) { - if (body->hasTagName(framesetTag) && !frameFlatteningEnabled()) { + if (is<HTMLFrameSetElement>(*body) && !frameFlatteningEnabled()) { body->renderer()->setChildNeedsLayout(); - } else if (body->hasTagName(bodyTag)) { - if (!m_firstLayout && m_size.height() != layoutHeight() && body->renderer()->enclosingBox()->stretchesToViewport()) + } else if (is<HTMLBodyElement>(*body)) { + if (!m_firstLayout && m_size.height() != layoutHeight() && body->renderer()->enclosingBox().stretchesToViewport()) body->renderer()->setChildNeedsLayout(); } } -#ifdef INSTRUMENT_LAYOUT_SCHEDULING +#if !LOG_DISABLED if (m_firstLayout && !frame().ownerElement()) - printf("Elapsed time before first layout: %lld\n", document.elapsedTime().count()); -#endif + LOG(Layout, "FrameView %p elapsed time before first layout: %.3fs\n", this, document.timeSinceDocumentCreation().value()); +#endif } - autoSizeIfEnabled(); - - m_needsFullRepaint = !subtree && (m_firstLayout || toRenderView(*root).printing()); + m_needsFullRepaint = !subtree && (m_firstLayout || downcast<RenderView>(*root).printing()); if (!subtree) { ScrollbarMode hMode; @@ -1231,11 +1431,7 @@ void FrameView::layout(bool allowSubtree) m_firstLayout = false; m_firstLayoutCallbackPending = true; - if (useFixedLayout() && !fixedLayoutSize().isEmpty() && delegatesScrolling()) - m_lastViewportSize = fixedLayoutSize(); - else - m_lastViewportSize = visibleContentRectIncludingScrollbars().size(); - + m_lastViewportSize = sizeForResizeEvent(); m_lastZoomFactor = root->style().zoom(); // Set the initial vMode to AlwaysOn if we're auto. @@ -1244,7 +1440,9 @@ void FrameView::layout(bool allowSubtree) // Set the initial hMode to AlwaysOff if we're auto. if (hMode == ScrollbarAuto) setHorizontalScrollbarMode(ScrollbarAlwaysOff); // This causes a horizontal scrollbar to disappear. - + Page* page = frame().page(); + if (page && page->expectsWheelEventTriggers()) + scrollAnimator().setWheelEventTestTrigger(page->testTrigger()); setScrollbarModes(hMode, vMode); setScrollbarsSuppressed(false, true); } else @@ -1252,14 +1450,15 @@ void FrameView::layout(bool allowSubtree) } LayoutSize oldSize = m_size; - m_size = layoutSize(); if (oldSize != m_size) { + LOG(Layout, " layout size changed from %.3fx%.3f to %.3fx%.3f", oldSize.width().toFloat(), oldSize.height().toFloat(), m_size.width().toFloat(), m_size.height().toFloat()); m_needsFullRepaint = true; if (!m_firstLayout) { - RenderBox* rootRenderer = document.documentElement() ? document.documentElement()->renderBox() : 0; - RenderBox* bodyRenderer = rootRenderer && document.body() ? document.body()->renderBox() : 0; + RenderBox* rootRenderer = document.documentElement() ? document.documentElement()->renderBox() : nullptr; + auto* body = document.bodyOrFrameset(); + RenderBox* bodyRenderer = rootRenderer && body ? body->renderBox() : nullptr; if (bodyRenderer && bodyRenderer->stretchesToViewport()) bodyRenderer->setChildNeedsLayout(); else if (rootRenderer && rootRenderer->stretchesToViewport()) @@ -1271,53 +1470,49 @@ void FrameView::layout(bool allowSubtree) } layer = root->enclosingLayer(); + SubtreeLayoutStateMaintainer subtreeLayoutStateMaintainer(m_layoutRoot); - bool disableLayoutState = false; - if (subtree) { - disableLayoutState = root->view().shouldDisableLayoutStateForSubtree(root); - root->view().pushLayoutState(*root); - } - LayoutStateDisabler layoutStateDisabler(disableLayoutState ? &root->view() : 0); RenderView::RepaintRegionAccumulator repaintRegionAccumulator(&root->view()); ASSERT(m_layoutPhase == InPreLayout); - m_layoutPhase = InLayout; + m_layoutPhase = InRenderTreeLayout; forceLayoutParentViewIfNeeded(); - ASSERT(m_layoutPhase == InLayout); - - root->layout(); -#if ENABLE(IOS_TEXT_AUTOSIZING) - float minZoomFontSize = frame().settings().minimumZoomFontSize(); - float visWidth = frame().page()->mainFrame().textAutosizingWidth(); - if (minZoomFontSize && visWidth && !root->view().printing()) { - root->adjustComputedFontSizesOnBlocks(minZoomFontSize, visWidth); - bool needsLayout = root->needsLayout(); - if (needsLayout) - root->layout(); - } + ASSERT(m_layoutPhase == InRenderTreeLayout); +#ifndef NDEBUG + RenderTreeNeedsLayoutChecker checker(*root); #endif + root->layout(); + ASSERT(!root->view().renderTreeIsBeingMutatedInternally()); + #if ENABLE(TEXT_AUTOSIZING) - if (document.textAutosizer()->processSubtree(root) && root->needsLayout()) - root->layout(); + if (frame().settings().textAutosizingEnabled() && !root->view().printing()) { + float minimumZoomFontSize = frame().settings().minimumZoomFontSize(); + float textAutosizingWidth = frame().page() ? frame().page()->textAutosizingWidth() : 0; + if (int overrideWidth = frame().settings().textAutosizingWindowSizeOverride().width()) + textAutosizingWidth = overrideWidth; + + LOG(TextAutosizing, "Text Autosizing: minimumZoomFontSize=%.2f textAutosizingWidth=%.2f", minimumZoomFontSize, textAutosizingWidth); + + if (minimumZoomFontSize && textAutosizingWidth) { + root->adjustComputedFontSizesOnBlocks(minimumZoomFontSize, textAutosizingWidth); + if (root->needsLayout()) + root->layout(); + } + } #endif - ASSERT(m_layoutPhase == InLayout); - - if (subtree) - root->view().popLayoutState(*root); - - m_layoutRoot = 0; - - // Close block here to end the scope of changeSchedulingEnabled and layoutStateDisabler. + ASSERT(m_layoutPhase == InRenderTreeLayout); + m_layoutRoot = nullptr; + // Close block here to end the scope of changeSchedulingEnabled and SubtreeLayoutStateMaintainer. } m_layoutPhase = InViewSizeAdjust; bool neededFullRepaint = m_needsFullRepaint; - if (!subtree && !toRenderView(*root).printing()) + if (!subtree && !downcast<RenderView>(*root).printing()) adjustViewSize(); m_layoutPhase = InPostLayout; @@ -1328,15 +1523,19 @@ void FrameView::layout(bool allowSubtree) if (m_needsFullRepaint) root->view().repaintRootContents(); + root->view().releaseProtectedRenderWidgets(); + + ASSERT(!root->needsLayout()); + layer->updateLayerPositionsAfterLayout(renderView()->layer(), updateLayerPositionFlags(layer, subtree, m_needsFullRepaint)); -#if USE(ACCELERATED_COMPOSITING) updateCompositingLayersAfterLayout(); -#endif - + + m_layoutPhase = InPostLayerPositionsUpdatedAfterLayout; + m_layoutCount++; -#if PLATFORM(MAC) || PLATFORM(WIN) || PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK) if (AXObjectCache* cache = root->document().existingAXObjectCache()) cache->postNotification(root, AXObjectCache::AXLayoutComplete); #endif @@ -1349,19 +1548,23 @@ void FrameView::layout(bool allowSubtree) document.dirtyTouchEventRects(); #endif - ASSERT(!root->needsLayout()); - updateCanBlitOnScrollRecursively(); + handleDeferredScrollUpdateAfterContentSizeChange(); + + handleDeferredScrollbarsUpdateAfterDirectionChange(); + if (document.hasListenerType(Document::OVERFLOWCHANGED_LISTENER)) updateOverflowStatus(layoutWidth() < contentsWidth(), layoutHeight() < contentsHeight()); + frame().document()->markers().invalidateRectsForAllMarkers(); + if (!m_postLayoutTasksTimer.isActive()) { if (!m_inSynchronousPostLayout) { if (inChildFrameLayoutWithFrameFlattening) updateWidgetPositions(); else { - TemporaryChange<bool> inSynchronousPostLayoutChange(m_inSynchronousPostLayout, true); + SetForScope<bool> inSynchronousPostLayoutChange(m_inSynchronousPostLayout, true); performPostLayoutTasks(); // Calls resumeScheduledEvents(). } } @@ -1372,37 +1575,33 @@ void FrameView::layout(bool allowSubtree) // can make us need to update again, and we can get stuck in a nasty cycle unless // we call it through the timer here. m_postLayoutTasksTimer.startOneShot(0); - if (needsLayout()) - layout(); } + if (needsLayout()) + layout(); } - InspectorInstrumentation::didLayout(cookie, root); + InspectorInstrumentation::didLayout(cookie, *root); + DebugPageOverlays::didLayout(frame()); --m_nestedLayoutCount; +} - if (m_nestedLayoutCount) - return; - - if (Page* page = frame().page()) - page->chrome().client().layoutUpdated(&frame()); +bool FrameView::shouldDeferScrollUpdateAfterContentSizeChange() +{ + return (m_layoutPhase < InPostLayout) && (m_layoutPhase != OutsideLayout); } RenderBox* FrameView::embeddedContentBox() const { -#if ENABLE(SVG) RenderView* renderView = this->renderView(); if (!renderView) return nullptr; RenderObject* firstChild = renderView->firstChild(); - if (!firstChild || !firstChild->isBox()) - return nullptr; // Curently only embedded SVG documents participate in the size-negotiation logic. - if (toRenderBox(firstChild)->isSVGRoot()) - return toRenderBox(firstChild); -#endif + if (is<RenderSVGRoot>(firstChild)) + return downcast<RenderSVGRoot>(firstChild); return nullptr; } @@ -1410,12 +1609,12 @@ RenderBox* FrameView::embeddedContentBox() const void FrameView::addEmbeddedObjectToUpdate(RenderEmbeddedObject& embeddedObject) { if (!m_embeddedObjectsToUpdate) - m_embeddedObjectsToUpdate = adoptPtr(new ListHashSet<RenderEmbeddedObject*>); + m_embeddedObjectsToUpdate = std::make_unique<ListHashSet<RenderEmbeddedObject*>>(); HTMLFrameOwnerElement& element = embeddedObject.frameOwnerElement(); - if (isHTMLObjectElement(element) || isHTMLEmbedElement(element)) { + if (is<HTMLObjectElement>(element) || is<HTMLEmbedElement>(element)) { // Tell the DOM element that it needs a widget update. - HTMLPlugInImageElement& pluginElement = toHTMLPlugInImageElement(element); + HTMLPlugInImageElement& pluginElement = downcast<HTMLPlugInImageElement>(element); if (!pluginElement.needsCheckForSizeChange()) pluginElement.setNeedsWidgetUpdate(true); } @@ -1440,7 +1639,7 @@ String FrameView::mediaType() const { // See if we have an override type. String overrideType = frame().loader().client().overrideMediaType(); - InspectorInstrumentation::applyEmulatedMedia(&frame(), &overrideType); + InspectorInstrumentation::applyEmulatedMedia(frame(), overrideType); if (!overrideType.isNull()) return overrideType; return m_mediaType; @@ -1451,7 +1650,7 @@ void FrameView::adjustMediaTypeForPrinting(bool printing) if (printing) { if (m_mediaTypeWhenNotPrinting.isNull()) m_mediaTypeWhenNotPrinting = mediaType(); - setMediaType("print"); + setMediaType("print"); } else { if (!m_mediaTypeWhenNotPrinting.isNull()) setMediaType(m_mediaTypeWhenNotPrinting); @@ -1467,7 +1666,7 @@ bool FrameView::useSlowRepaints(bool considerOverlap) const // m_contentIsOpaque, so don't take the fast path for composited layers // if they are a platform widget in order to get painting correctness // for transparent layers. See the comment in WidgetMac::paint. - if (contentsInCompositedLayer() && !platformWidget()) + if (usesCompositedScrolling() && !platformWidget()) return mustBeSlow; bool isOverlapped = m_isOverlapped && considerOverlap; @@ -1488,25 +1687,53 @@ bool FrameView::useSlowRepaintsIfNotOverlapped() const void FrameView::updateCanBlitOnScrollRecursively() { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) { if (FrameView* view = frame->view()) view->setCanBlitOnScroll(!view->useSlowRepaints()); } } -bool FrameView::contentsInCompositedLayer() const +bool FrameView::usesCompositedScrolling() const { -#if USE(ACCELERATED_COMPOSITING) RenderView* renderView = this->renderView(); if (renderView && renderView->isComposited()) { GraphicsLayer* layer = renderView->layer()->backing()->graphicsLayer(); if (layer && layer->drawsContent()) return true; } + + return false; +} + +bool FrameView::usesAsyncScrolling() const +{ +#if ENABLE(ASYNC_SCROLLING) + if (Page* page = frame().page()) { + if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) + return scrollingCoordinator->coordinatesScrollingForFrameView(*this); + } #endif return false; } +bool FrameView::usesMockScrollAnimator() const +{ + return Settings::usesMockScrollAnimator(); +} + +void FrameView::logMockScrollAnimatorMessage(const String& message) const +{ + Document* document = frame().document(); + if (!document) + return; + StringBuilder builder; + if (frame().isMainFrame()) + builder.appendLiteral("Main"); + builder.appendLiteral("FrameView: "); + builder.append(message); + document->addConsoleMessage(MessageSource::Other, MessageLevel::Debug, builder.toString()); +} + void FrameView::setCannotBlitToWindow() { m_cannotBlitToWindow = true; @@ -1518,7 +1745,7 @@ void FrameView::addSlowRepaintObject(RenderElement* o) bool hadSlowRepaintObjects = hasSlowRepaintObjects(); if (!m_slowRepaintObjects) - m_slowRepaintObjects = adoptPtr(new HashSet<RenderElement*>); + m_slowRepaintObjects = std::make_unique<HashSet<const RenderElement*>>(); m_slowRepaintObjects->add(o); @@ -1527,7 +1754,7 @@ void FrameView::addSlowRepaintObject(RenderElement* o) if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - scrollingCoordinator->frameViewHasSlowRepaintObjectsDidChange(this); + scrollingCoordinator->frameViewHasSlowRepaintObjectsDidChange(*this); } } } @@ -1544,7 +1771,7 @@ void FrameView::removeSlowRepaintObject(RenderElement* o) if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - scrollingCoordinator->frameViewHasSlowRepaintObjectsDidChange(this); + scrollingCoordinator->frameViewHasSlowRepaintObjectsDidChange(*this); } } } @@ -1552,7 +1779,7 @@ void FrameView::removeSlowRepaintObject(RenderElement* o) void FrameView::addViewportConstrainedObject(RenderElement* object) { if (!m_viewportConstrainedObjects) - m_viewportConstrainedObjects = adoptPtr(new ViewportConstrainedObjectSet); + m_viewportConstrainedObjects = std::make_unique<ViewportConstrainedObjectSet>(); if (!m_viewportConstrainedObjects->contains(object)) { m_viewportConstrainedObjects->add(object); @@ -1561,7 +1788,7 @@ void FrameView::addViewportConstrainedObject(RenderElement* object) if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - scrollingCoordinator->frameViewFixedObjectsDidChange(this); + scrollingCoordinator->frameViewFixedObjectsDidChange(*this); } } } @@ -1571,7 +1798,7 @@ void FrameView::removeViewportConstrainedObject(RenderElement* object) if (m_viewportConstrainedObjects && m_viewportConstrainedObjects->remove(object)) { if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - scrollingCoordinator->frameViewFixedObjectsDidChange(this); + scrollingCoordinator->frameViewFixedObjectsDidChange(*this); } // FIXME: In addFixedObject() we only call this if there's a platform widget, @@ -1580,20 +1807,214 @@ void FrameView::removeViewportConstrainedObject(RenderElement* object) } } +// visualViewport and layoutViewport are both in content coordinates (unzoomed). +LayoutPoint FrameView::computeLayoutViewportOrigin(const LayoutRect& visualViewport, const LayoutPoint& stableLayoutViewportOriginMin, const LayoutPoint& stableLayoutViewportOriginMax, const LayoutRect& layoutViewport, ScrollBehaviorForFixedElements fixedBehavior) +{ + LayoutPoint layoutViewportOrigin = layoutViewport.location(); + bool allowRubberBanding = fixedBehavior == StickToViewportBounds; + + if (visualViewport.width() > layoutViewport.width()) + layoutViewportOrigin.setX(visualViewport.x()); + else { + bool rubberbandingAtLeft = allowRubberBanding && visualViewport.x() < stableLayoutViewportOriginMin.x(); + bool rubberbandingAtRight = allowRubberBanding && (visualViewport.maxX() - layoutViewport.width()) > stableLayoutViewportOriginMax.x(); + + if (visualViewport.x() < layoutViewport.x() || rubberbandingAtLeft) + layoutViewportOrigin.setX(visualViewport.x()); + + if (visualViewport.maxX() > layoutViewport.maxX() || rubberbandingAtRight) + layoutViewportOrigin.setX(visualViewport.maxX() - layoutViewport.width()); + + if (!rubberbandingAtLeft && layoutViewportOrigin.x() < stableLayoutViewportOriginMin.x()) + layoutViewportOrigin.setX(stableLayoutViewportOriginMin.x()); + + if (!rubberbandingAtRight && layoutViewportOrigin.x() > stableLayoutViewportOriginMax.x()) + layoutViewportOrigin.setX(stableLayoutViewportOriginMax.x()); + } + + if (visualViewport.height() > layoutViewport.height()) + layoutViewportOrigin.setY(visualViewport.y()); + else { + bool rubberbandingAtTop = allowRubberBanding && visualViewport.y() < stableLayoutViewportOriginMin.y(); + bool rubberbandingAtBottom = allowRubberBanding && (visualViewport.maxY() - layoutViewport.height()) > stableLayoutViewportOriginMax.y(); + + if (visualViewport.y() < layoutViewport.y() || rubberbandingAtTop) + layoutViewportOrigin.setY(visualViewport.y()); + + if (visualViewport.maxY() > layoutViewport.maxY() || rubberbandingAtBottom) + layoutViewportOrigin.setY(visualViewport.maxY() - layoutViewport.height()); + + if (!rubberbandingAtTop && layoutViewportOrigin.y() < stableLayoutViewportOriginMin.y()) + layoutViewportOrigin.setY(stableLayoutViewportOriginMin.y()); + + if (!rubberbandingAtBottom && layoutViewportOrigin.y() > stableLayoutViewportOriginMax.y()) + layoutViewportOrigin.setY(stableLayoutViewportOriginMax.y()); + } + + return layoutViewportOrigin; +} + +void FrameView::setBaseLayoutViewportOrigin(LayoutPoint origin, TriggerLayoutOrNot layoutTriggering) +{ + ASSERT(frame().settings().visualViewportEnabled()); + + if (origin == m_layoutViewportOrigin) + return; + + m_layoutViewportOrigin = origin; + if (layoutTriggering == TriggerLayoutOrNot::Yes) + setViewportConstrainedObjectsNeedLayout(); + + if (TiledBacking* tiledBacking = this->tiledBacking()) { + FloatRect layoutViewport = layoutViewportRect(); + layoutViewport.moveBy(unscaledScrollOrigin()); // tiledBacking deals in top-left relative coordinates. + tiledBacking->setLayoutViewportRect(layoutViewport); + } +} + +void FrameView::setLayoutViewportOverrideRect(std::optional<LayoutRect> rect) +{ + if (rect == m_layoutViewportOverrideRect) + return; + + LayoutRect oldRect = layoutViewportRect(); + m_layoutViewportOverrideRect = rect; + + LOG_WITH_STREAM(Scrolling, stream << "\nFrameView " << this << " setLayoutViewportOverrideRect() - changing layout viewport from " << oldRect << " to " << m_layoutViewportOverrideRect.value()); + + if (oldRect != layoutViewportRect()) + setViewportConstrainedObjectsNeedLayout(); +} + +LayoutSize FrameView::baseLayoutViewportSize() const +{ + return renderView() ? renderView()->size() : size(); +} + +void FrameView::updateLayoutViewport() +{ + if (!frame().settings().visualViewportEnabled()) + return; + + // Don't update the layout viewport if we're in the middle of adjusting scrollbars. We'll get another call + // as a post-layout task. + if (m_layoutPhase == InViewSizeAdjust) + return; + + if (m_layoutViewportOverrideRect) { + LOG_WITH_STREAM(Scrolling, stream << "\nFrameView " << this << " updateLayoutViewport() - has layoutViewportOverrideRect" << m_layoutViewportOverrideRect.value()); + return; + } + + LayoutRect layoutViewport = layoutViewportRect(); + + LOG_WITH_STREAM(Scrolling, stream << "\nFrameView " << this << " updateLayoutViewport() totalContentSize " << totalContentsSize() << " unscaledDocumentRect " << (renderView() ? renderView()->unscaledDocumentRect() : IntRect()) << " header height " << headerHeight() << " footer height " << footerHeight() << " fixed behavior " << scrollBehaviorForFixedElements()); + LOG_WITH_STREAM(Scrolling, stream << "layoutViewport: " << layoutViewport); + LOG_WITH_STREAM(Scrolling, stream << "visualViewport: " << visualViewportRect()); + LOG_WITH_STREAM(Scrolling, stream << "scroll positions: min: " << unscaledMinimumScrollPosition() << " max: "<< unscaledMaximumScrollPosition()); + + LayoutPoint newLayoutViewportOrigin = computeLayoutViewportOrigin(visualViewportRect(), minStableLayoutViewportOrigin(), maxStableLayoutViewportOrigin(), layoutViewport, scrollBehaviorForFixedElements()); + if (newLayoutViewportOrigin != m_layoutViewportOrigin) { + setBaseLayoutViewportOrigin(newLayoutViewportOrigin); + LOG_WITH_STREAM(Scrolling, stream << "layoutViewport changed to " << layoutViewportRect()); + } +} + +LayoutPoint FrameView::minStableLayoutViewportOrigin() const +{ + return unscaledMinimumScrollPosition(); +} + +LayoutPoint FrameView::maxStableLayoutViewportOrigin() const +{ + LayoutPoint maxPosition = unscaledMaximumScrollPosition(); + maxPosition = (maxPosition - LayoutSize(0, headerHeight() + footerHeight())).expandedTo({ }); + return maxPosition; +} + +IntPoint FrameView::unscaledScrollOrigin() const +{ + if (RenderView* renderView = this->renderView()) + return -renderView->unscaledDocumentRect().location(); // Akin to code in adjustViewSize(). + + return { }; +} + +LayoutRect FrameView::layoutViewportRect() const +{ + if (m_layoutViewportOverrideRect) + return m_layoutViewportOverrideRect.value(); + + // Size of initial containing block, anchored at scroll position, in document coordinates (unchanged by scale factor). + return LayoutRect(m_layoutViewportOrigin, renderView() ? renderView()->size() : size()); +} + +// visibleContentRect is in the bounds of the scroll view content. That consists of an +// optional header, the document, and an optional footer. Only the document is scaled, +// so we have to compute the visible part of the document in unscaled document coordinates. +// On iOS, pageScaleFactor is always 1 here, and we never have headers and footers. +LayoutRect FrameView::visibleDocumentRect(const FloatRect& visibleContentRect, float headerHeight, float footerHeight, const FloatSize& totalContentsSize, float pageScaleFactor) +{ + float contentsHeight = totalContentsSize.height() - headerHeight - footerHeight; + + float rubberBandTop = std::min<float>(visibleContentRect.y(), 0); + float visibleScaledDocumentTop = std::max<float>(visibleContentRect.y() - headerHeight, 0) + rubberBandTop; + + float rubberBandBottom = std::min<float>((totalContentsSize.height() - visibleContentRect.y()) - visibleContentRect.height(), 0); + float visibleScaledDocumentBottom = std::min<float>(visibleContentRect.maxY() - headerHeight, contentsHeight) - rubberBandBottom; + + FloatRect visibleDocumentRect = visibleContentRect; + visibleDocumentRect.setY(visibleScaledDocumentTop); + visibleDocumentRect.setHeight(std::max<float>(visibleScaledDocumentBottom - visibleScaledDocumentTop, 0)); + visibleDocumentRect.scale(1 / pageScaleFactor); + + return LayoutRect(visibleDocumentRect); +} + +LayoutRect FrameView::visualViewportRect() const +{ + FloatRect visibleContentRect = this->visibleContentRect(LegacyIOSDocumentVisibleRect); + return visibleDocumentRect(visibleContentRect, headerHeight(), footerHeight(), totalContentsSize(), frameScaleFactor()); +} + LayoutRect FrameView::viewportConstrainedVisibleContentRect() const { + ASSERT(!frame().settings().visualViewportEnabled()); + #if PLATFORM(IOS) if (useCustomFixedPositionLayoutRect()) return customFixedPositionLayoutRect(); #endif LayoutRect viewportRect = visibleContentRect(); - viewportRect.setLocation(toPoint(scrollOffsetForFixedPosition())); + + viewportRect.setLocation(scrollPositionForFixedPosition()); return viewportRect; } -IntSize FrameView::scrollOffsetForFixedPosition(const IntRect& visibleContentRect, const IntSize& totalContentsSize, const IntPoint& scrollPosition, const IntPoint& scrollOrigin, float frameScaleFactor, bool fixedElementsLayoutRelativeToFrame, ScrollBehaviorForFixedElements behaviorForFixed, int headerHeight, int footerHeight) +LayoutRect FrameView::rectForFixedPositionLayout() const { - IntPoint position; + if (frame().settings().visualViewportEnabled()) + return layoutViewportRect(); + + return viewportConstrainedVisibleContentRect(); +} + +float FrameView::frameScaleFactor() const +{ + return frame().frameScaleFactor(); +} + +LayoutPoint FrameView::scrollPositionForFixedPosition() const +{ + if (frame().settings().visualViewportEnabled()) + return layoutViewportRect().location(); + + return scrollPositionForFixedPosition(visibleContentRect(), totalContentsSize(), scrollPosition(), scrollOrigin(), frameScaleFactor(), fixedElementsLayoutRelativeToFrame(), scrollBehaviorForFixedElements(), headerHeight(), footerHeight()); +} + +LayoutPoint FrameView::scrollPositionForFixedPosition(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, const LayoutPoint& scrollPosition, const LayoutPoint& scrollOrigin, float frameScaleFactor, bool fixedElementsLayoutRelativeToFrame, ScrollBehaviorForFixedElements behaviorForFixed, int headerHeight, int footerHeight) +{ + LayoutPoint position; if (behaviorForFixed == StickToDocumentBounds) position = ScrollableArea::constrainScrollPositionForOverhang(visibleContentRect, totalContentsSize, scrollPosition, scrollOrigin, headerHeight, footerHeight); else { @@ -1601,28 +2022,110 @@ IntSize FrameView::scrollOffsetForFixedPosition(const IntRect& visibleContentRec position.setY(position.y() - headerHeight); } - IntSize maxSize = totalContentsSize - visibleContentRect.size(); + LayoutSize maxSize = totalContentsSize - visibleContentRect.size(); float dragFactorX = (fixedElementsLayoutRelativeToFrame || !maxSize.width()) ? 1 : (totalContentsSize.width() - visibleContentRect.width() * frameScaleFactor) / maxSize.width(); float dragFactorY = (fixedElementsLayoutRelativeToFrame || !maxSize.height()) ? 1 : (totalContentsSize.height() - visibleContentRect.height() * frameScaleFactor) / maxSize.height(); - return IntSize(position.x() * dragFactorX / frameScaleFactor, position.y() * dragFactorY / frameScaleFactor); + return LayoutPoint(position.x() * dragFactorX / frameScaleFactor, position.y() * dragFactorY / frameScaleFactor); +} + +float FrameView::yPositionForInsetClipLayer(const FloatPoint& scrollPosition, float topContentInset) +{ + if (!topContentInset) + return 0; + + // The insetClipLayer should not move for negative scroll values. + float scrollY = std::max<float>(0, scrollPosition.y()); + + if (scrollY >= topContentInset) + return 0; + + return topContentInset - scrollY; } -IntSize FrameView::scrollOffsetForFixedPosition() const +float FrameView::yPositionForHeaderLayer(const FloatPoint& scrollPosition, float topContentInset) +{ + if (!topContentInset) + return 0; + + float scrollY = std::max<float>(0, scrollPosition.y()); + + if (scrollY >= topContentInset) + return topContentInset; + + return scrollY; +} + +float FrameView::yPositionForFooterLayer(const FloatPoint& scrollPosition, float topContentInset, float totalContentsHeight, float footerHeight) +{ + return yPositionForHeaderLayer(scrollPosition, topContentInset) + totalContentsHeight - footerHeight; +} + +FloatPoint FrameView::positionForRootContentLayer(const FloatPoint& scrollPosition, const FloatPoint& scrollOrigin, float topContentInset, float headerHeight) +{ + return FloatPoint(0, yPositionForHeaderLayer(scrollPosition, topContentInset) + headerHeight) - toFloatSize(scrollOrigin); +} + +FloatPoint FrameView::positionForRootContentLayer() const +{ + return positionForRootContentLayer(scrollPosition(), scrollOrigin(), topContentInset(), headerHeight()); +} + +#if PLATFORM(IOS) +LayoutRect FrameView::rectForViewportConstrainedObjects(const LayoutRect& visibleContentRect, const LayoutSize& totalContentsSize, float frameScaleFactor, bool fixedElementsLayoutRelativeToFrame, ScrollBehaviorForFixedElements scrollBehavior) +{ + if (fixedElementsLayoutRelativeToFrame) + return visibleContentRect; + + if (totalContentsSize.isEmpty()) + return visibleContentRect; + + // We impose an lower limit on the size (so an upper limit on the scale) of + // the rect used to position fixed objects so that they don't crowd into the + // center of the screen at larger scales. + const LayoutUnit maxContentWidthForZoomThreshold = LayoutUnit::fromPixel(1024); + float zoomedOutScale = frameScaleFactor * visibleContentRect.width() / std::min(maxContentWidthForZoomThreshold, totalContentsSize.width()); + float constraintThresholdScale = 1.5 * zoomedOutScale; + float maxPostionedObjectsRectScale = std::min(frameScaleFactor, constraintThresholdScale); + + LayoutRect viewportConstrainedObjectsRect = visibleContentRect; + + if (frameScaleFactor > constraintThresholdScale) { + FloatRect contentRect(FloatPoint(), totalContentsSize); + FloatRect viewportRect = visibleContentRect; + + // Scale the rect up from a point that is relative to its position in the viewport. + FloatSize sizeDelta = contentRect.size() - viewportRect.size(); + + FloatPoint scaleOrigin; + scaleOrigin.setX(contentRect.x() + sizeDelta.width() > 0 ? contentRect.width() * (viewportRect.x() - contentRect.x()) / sizeDelta.width() : 0); + scaleOrigin.setY(contentRect.y() + sizeDelta.height() > 0 ? contentRect.height() * (viewportRect.y() - contentRect.y()) / sizeDelta.height() : 0); + + AffineTransform rescaleTransform = AffineTransform::translation(scaleOrigin.x(), scaleOrigin.y()); + rescaleTransform.scale(frameScaleFactor / maxPostionedObjectsRectScale, frameScaleFactor / maxPostionedObjectsRectScale); + rescaleTransform = CGAffineTransformTranslate(rescaleTransform, -scaleOrigin.x(), -scaleOrigin.y()); + + viewportConstrainedObjectsRect = enclosingLayoutRect(rescaleTransform.mapRect(visibleContentRect)); + } + + if (scrollBehavior == StickToDocumentBounds) { + LayoutRect documentBounds(LayoutPoint(), totalContentsSize); + viewportConstrainedObjectsRect.intersect(documentBounds); + } + + return viewportConstrainedObjectsRect; +} + +LayoutRect FrameView::viewportConstrainedObjectsRect() const { - IntRect visibleContentRect = this->visibleContentRect(); - IntSize totalContentsSize = this->totalContentsSize(); - IntPoint scrollPosition = this->scrollPosition(); - IntPoint scrollOrigin = this->scrollOrigin(); - float frameScaleFactor = frame().frameScaleFactor(); - ScrollBehaviorForFixedElements behaviorForFixed = scrollBehaviorForFixedElements(); - return scrollOffsetForFixedPosition(visibleContentRect, totalContentsSize, scrollPosition, scrollOrigin, frameScaleFactor, fixedElementsLayoutRelativeToFrame(), behaviorForFixed, headerHeight(), footerHeight()); + return rectForViewportConstrainedObjects(visibleContentRect(), totalContentsSize(), frame().frameScaleFactor(), fixedElementsLayoutRelativeToFrame(), scrollBehaviorForFixedElements()); } +#endif -IntPoint FrameView::minimumScrollPosition() const +ScrollPosition FrameView::minimumScrollPosition() const { - IntPoint minimumPosition(ScrollView::minimumScrollPosition()); + ScrollPosition minimumPosition = ScrollView::minimumScrollPosition(); if (frame().isMainFrame() && m_scrollPinningBehavior == PinToBottom) minimumPosition.setY(maximumScrollPosition().y()); @@ -1630,16 +2133,66 @@ IntPoint FrameView::minimumScrollPosition() const return minimumPosition; } -IntPoint FrameView::maximumScrollPosition() const +ScrollPosition FrameView::maximumScrollPosition() const { - IntPoint maximumOffset(contentsWidth() - visibleWidth() - scrollOrigin().x(), totalContentsSize().height() - visibleHeight() - scrollOrigin().y()); - - maximumOffset.clampNegativeToZero(); + ScrollPosition maximumPosition = ScrollView::maximumScrollPosition(); if (frame().isMainFrame() && m_scrollPinningBehavior == PinToTop) - maximumOffset.setY(minimumScrollPosition().y()); + maximumPosition.setY(minimumScrollPosition().y()); - return maximumOffset; + return maximumPosition; +} + +ScrollPosition FrameView::unscaledMinimumScrollPosition() const +{ + if (RenderView* renderView = this->renderView()) { + IntRect unscaledDocumentRect = renderView->unscaledDocumentRect(); + ScrollPosition minimumPosition = unscaledDocumentRect.location(); + + if (frame().isMainFrame() && m_scrollPinningBehavior == PinToBottom) + minimumPosition.setY(unscaledMaximumScrollPosition().y()); + + return minimumPosition; + } + + return minimumScrollPosition(); +} + +ScrollPosition FrameView::unscaledMaximumScrollPosition() const +{ + if (RenderView* renderView = this->renderView()) { + IntRect unscaledDocumentRect = renderView->unscaledDocumentRect(); + unscaledDocumentRect.expand(0, headerHeight() + footerHeight()); + ScrollPosition maximumPosition = ScrollPosition(unscaledDocumentRect.maxXMaxYCorner() - visibleSize()).expandedTo({ 0, 0 }); + + if (frame().isMainFrame() && m_scrollPinningBehavior == PinToTop) + maximumPosition.setY(unscaledMinimumScrollPosition().y()); + + return maximumPosition; + } + + return maximumScrollPosition(); +} + +void FrameView::viewportContentsChanged() +{ + if (!frame().view()) { + // The frame is being destroyed. + return; + } + + if (auto* page = frame().page()) + page->updateValidationBubbleStateIfNeeded(); + + // When the viewport contents changes (scroll, resize, style recalc, layout, ...), + // check if we should resume animated images or unthrottle DOM timers. + applyRecursivelyWithVisibleRect([] (FrameView& frameView, const IntRect& visibleRect) { + frameView.resumeVisibleImageAnimations(visibleRect); + frameView.updateScriptedAnimationsAndTimersThrottlingState(visibleRect); + + if (auto* renderView = frameView.frame().contentRenderer()) + renderView->updateVisibleViewportRect(visibleRect); + }); } bool FrameView::fixedElementsLayoutRelativeToFrame() const @@ -1666,42 +2219,38 @@ bool FrameView::shouldSetCursor() const bool FrameView::scrollContentsFastPath(const IntSize& scrollDelta, const IntRect& rectToScroll, const IntRect& clipRect) { if (!m_viewportConstrainedObjects || m_viewportConstrainedObjects->isEmpty()) { - hostWindow()->scroll(scrollDelta, rectToScroll, clipRect); + frame().page()->chrome().scroll(scrollDelta, rectToScroll, clipRect); return true; } - const bool isCompositedContentLayer = contentsInCompositedLayer(); + bool isCompositedContentLayer = usesCompositedScrolling(); // Get the rects of the fixed objects visible in the rectToScroll Region regionToUpdate; for (auto& renderer : *m_viewportConstrainedObjects) { if (!renderer->style().hasViewportConstrainedPosition()) continue; -#if USE(ACCELERATED_COMPOSITING) if (renderer->isComposited()) continue; -#endif // Fixed items should always have layers. ASSERT(renderer->hasLayer()); - RenderLayer* layer = toRenderBoxModelObject(renderer)->layer(); + RenderLayer* layer = downcast<RenderBoxModelObject>(*renderer).layer(); -#if USE(ACCELERATED_COMPOSITING) if (layer->viewportConstrainedNotCompositedReason() == RenderLayer::NotCompositedForBoundsOutOfView || layer->viewportConstrainedNotCompositedReason() == RenderLayer::NotCompositedForNoVisibleContent) { // Don't invalidate for invisible fixed layers. continue; } -#endif -#if ENABLE(CSS_FILTERS) if (layer->hasAncestorWithFilterOutsets()) { // If the fixed layer has a blur/drop-shadow filter applied on at least one of its parents, we cannot // scroll using the fast path, otherwise the outsets of the filter will be moved around the page. return false; } -#endif - IntRect updateRect = pixelSnappedIntRect(layer->repaintRectIncludingNonCompositingDescendants()); + + // FIXME: use pixel snapping instead of enclosing when ScrollView has finished transitioning from IntRect to Float/LayoutRect. + IntRect updateRect = enclosingIntRect(layer->repaintRectIncludingNonCompositingDescendants()); updateRect = contentsToRootView(updateRect); if (!isCompositedContentLayer && clipsRepaints()) updateRect.intersect(rectToScroll); @@ -1710,27 +2259,22 @@ bool FrameView::scrollContentsFastPath(const IntSize& scrollDelta, const IntRect } // 1) scroll - hostWindow()->scroll(scrollDelta, rectToScroll, clipRect); + frame().page()->chrome().scroll(scrollDelta, rectToScroll, clipRect); // 2) update the area of fixed objects that has been invalidated - Vector<IntRect> subRectsToUpdate = regionToUpdate.rects(); - size_t viewportConstrainedObjectsCount = subRectsToUpdate.size(); - for (size_t i = 0; i < viewportConstrainedObjectsCount; ++i) { - IntRect updateRect = subRectsToUpdate[i]; + for (auto& updateRect : regionToUpdate.rects()) { IntRect scrolledRect = updateRect; scrolledRect.move(scrollDelta); updateRect.unite(scrolledRect); -#if USE(ACCELERATED_COMPOSITING) if (isCompositedContentLayer) { updateRect = rootViewToContents(updateRect); ASSERT(renderView()); renderView()->layer()->setBackingNeedsRepaintInRect(updateRect); continue; } -#endif if (clipsRepaints()) updateRect.intersect(rectToScroll); - hostWindow()->invalidateContentsAndRootView(updateRect, false); + frame().page()->chrome().invalidateContentsAndRootView(updateRect); } return true; @@ -1738,31 +2282,16 @@ bool FrameView::scrollContentsFastPath(const IntSize& scrollDelta, const IntRect void FrameView::scrollContentsSlowPath(const IntRect& updateRect) { -#if USE(ACCELERATED_COMPOSITING) - if (contentsInCompositedLayer()) { - // FIXME: respect paintsEntireContents()? - IntRect updateRect = visibleContentRect(LegacyIOSDocumentVisibleRect); - - // Make sure to "apply" the scale factor here since we're converting from frame view - // coordinates to layer backing coordinates. - updateRect.scale(1 / frame().frameScaleFactor()); - - ASSERT(renderView()); - renderView()->layer()->setBackingNeedsRepaintInRect(updateRect); - } - repaintSlowRepaintObjects(); - if (RenderWidget* frameRenderer = frame().ownerRenderer()) { - if (isEnclosedInCompositingLayer()) { - LayoutRect rect(frameRenderer->borderLeft() + frameRenderer->paddingLeft(), - frameRenderer->borderTop() + frameRenderer->paddingTop(), - visibleWidth(), visibleHeight()); + if (!usesCompositedScrolling() && isEnclosedInCompositingLayer()) { + if (RenderWidget* frameRenderer = frame().ownerRenderer()) { + LayoutRect rect(frameRenderer->borderLeft() + frameRenderer->paddingLeft(), frameRenderer->borderTop() + frameRenderer->paddingTop(), + visibleWidth(), visibleHeight()); frameRenderer->repaintRectangle(rect); return; } } -#endif ScrollView::scrollContentsSlowPath(updateRect); } @@ -1775,7 +2304,7 @@ void FrameView::repaintSlowRepaintObjects() // Renderers with fixed backgrounds may be in compositing layers, so we need to explicitly // repaint them after scrolling. for (auto& renderer : *m_slowRepaintObjects) - renderer->repaint(); + renderer->repaintSlowRepaintObject(); } // Note that this gets called at painting time. @@ -1786,45 +2315,6 @@ void FrameView::setIsOverlapped(bool isOverlapped) m_isOverlapped = isOverlapped; updateCanBlitOnScrollRecursively(); - -#if USE(ACCELERATED_COMPOSITING) - if (hasCompositedContentIncludingDescendants()) { - // Overlap can affect compositing tests, so if it changes, we need to trigger - // a layer update in the parent document. - if (Frame* parentFrame = frame().tree().parent()) { - if (RenderView* parentView = parentFrame->contentRenderer()) { - RenderLayerCompositor& compositor = parentView->compositor(); - compositor.setCompositingLayersNeedRebuild(); - compositor.scheduleCompositingLayerUpdate(); - } - } - - if (RenderLayerCompositor::allowsIndependentlyCompositedFrames(this)) { - // We also need to trigger reevaluation for this and all descendant frames, - // since a frame uses compositing if any ancestor is compositing. - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { - if (RenderView* view = frame->contentRenderer()) { - RenderLayerCompositor& compositor = view->compositor(); - compositor.setCompositingLayersNeedRebuild(); - compositor.scheduleCompositingLayerUpdate(); - } - } - } - } -#endif -} - -bool FrameView::isOverlappedIncludingAncestors() const -{ - if (isOverlapped()) - return true; - - if (FrameView* parentView = parentFrameView()) { - if (parentView->isOverlapped()) - return true; - } - - return false; } void FrameView::setContentIsOpaque(bool contentIsOpaque) @@ -1847,8 +2337,10 @@ bool FrameView::scrollToFragment(const URL& url) // OTOH If CSS target was set previously, we want to set it to 0, recalc // and possibly repaint because :target pseudo class may have been // set (see bug 11321). - if (!url.hasFragmentIdentifier() && !frame().document()->cssTarget()) + if (!url.hasFragmentIdentifier()) { + frame().document()->setCSSTarget(nullptr); return false; + } String fragmentIdentifier = url.fragmentIdentifier(); if (scrollToAnchor(fragmentIdentifier)) @@ -1864,43 +2356,51 @@ bool FrameView::scrollToFragment(const URL& url) bool FrameView::scrollToAnchor(const String& name) { ASSERT(frame().document()); + auto& document = *frame().document(); - if (!frame().document()->haveStylesheetsLoaded()) { - frame().document()->setGotoAnchorNeededAfterStylesheetsLoad(true); + if (!document.haveStylesheetsLoaded()) { + document.setGotoAnchorNeededAfterStylesheetsLoad(true); return false; } - frame().document()->setGotoAnchorNeededAfterStylesheetsLoad(false); + document.setGotoAnchorNeededAfterStylesheetsLoad(false); - Element* anchorElement = frame().document()->findAnchor(name); + Element* anchorElement = document.findAnchor(name); // Setting to null will clear the current target. - frame().document()->setCSSTarget(anchorElement); + document.setCSSTarget(anchorElement); -#if ENABLE(SVG) - if (frame().document()->isSVGDocument()) { - if (SVGSVGElement* svg = toSVGDocument(frame().document())->rootElement()) { - svg->setupInitialView(name, anchorElement); + if (is<SVGDocument>(document)) { + if (auto* rootElement = SVGDocument::rootElement(document)) { + rootElement->scrollToAnchor(name, anchorElement); if (!anchorElement) return true; } } -#endif - + // Implement the rule that "" and "top" both mean top of page as in other browsers. - if (!anchorElement && !(name.isEmpty() || equalIgnoringCase(name, "top"))) + if (!anchorElement && !(name.isEmpty() || equalLettersIgnoringASCIICase(name, "top"))) return false; - maintainScrollPositionAtAnchor(anchorElement ? static_cast<Node*>(anchorElement) : frame().document()); + ContainerNode* scrollPositionAnchor = anchorElement; + if (!scrollPositionAnchor) + scrollPositionAnchor = frame().document(); + maintainScrollPositionAtAnchor(scrollPositionAnchor); // If the anchor accepts keyboard focus, move focus there to aid users relying on keyboard navigation. - if (anchorElement && anchorElement->isFocusable()) - frame().document()->setFocusedElement(anchorElement); + if (anchorElement) { + if (anchorElement->isFocusable()) + document.setFocusedElement(anchorElement); + else { + document.setFocusedElement(nullptr); + document.setFocusNavigationStartingNode(anchorElement); + } + } return true; } -void FrameView::maintainScrollPositionAtAnchor(Node* anchorNode) +void FrameView::maintainScrollPositionAtAnchor(ContainerNode* anchorNode) { m_maintainScrollPositionAnchor = anchorNode; if (!m_maintainScrollPositionAnchor) @@ -1917,33 +2417,43 @@ void FrameView::maintainScrollPositionAtAnchor(Node* anchorNode) scrollToAnchor(); } -void FrameView::scrollElementToRect(Element* element, const IntRect& rect) +void FrameView::scrollElementToRect(const Element& element, const IntRect& rect) { frame().document()->updateLayoutIgnorePendingStylesheets(); - LayoutRect bounds = element->boundingBox(); + LayoutRect bounds; + if (RenderElement* renderer = element.renderer()) + bounds = renderer->absoluteAnchorRect(); int centeringOffsetX = (rect.width() - bounds.width()) / 2; int centeringOffsetY = (rect.height() - bounds.height()) / 2; setScrollPosition(IntPoint(bounds.x() - centeringOffsetX - rect.x(), bounds.y() - centeringOffsetY - rect.y())); } -void FrameView::setScrollPosition(const IntPoint& scrollPoint) +void FrameView::setScrollPosition(const ScrollPosition& scrollPosition) { - TemporaryChange<bool> changeInProgrammaticScroll(m_inProgrammaticScroll, true); - m_maintainScrollPositionAnchor = 0; - ScrollView::setScrollPosition(scrollPoint); + SetForScope<bool> changeInProgrammaticScroll(m_inProgrammaticScroll, true); + m_maintainScrollPositionAnchor = nullptr; + Page* page = frame().page(); + if (page && page->expectsWheelEventTriggers()) + scrollAnimator().setWheelEventTestTrigger(page->testTrigger()); + ScrollView::setScrollPosition(scrollPosition); +} + +void FrameView::contentsResized() +{ + // For non-delegated scrolling, updateTiledBackingAdaptiveSizing() is called via addedOrRemovedScrollbar() which occurs less often. + if (delegatesScrolling()) + updateTiledBackingAdaptiveSizing(); } void FrameView::delegatesScrollingDidChange() { -#if USE(ACCELERATED_COMPOSITING) // When we switch to delgatesScrolling mode, we should destroy the scrolling/clipping layers in RenderLayerCompositor. if (hasCompositedContent()) clearBackingStores(); -#endif } -#if USE(TILED_BACKING_STORE) +#if USE(COORDINATED_GRAPHICS) void FrameView::setFixedVisibleContentRect(const IntRect& visibleContentRect) { bool visibleContentSizeDidChange = false; @@ -1954,20 +2464,21 @@ void FrameView::setFixedVisibleContentRect(const IntRect& visibleContentRect) visibleContentSizeDidChange = true; } - IntSize offset = scrollOffset(); + IntPoint oldPosition = scrollPosition(); ScrollView::setFixedVisibleContentRect(visibleContentRect); - if (offset != scrollOffset()) { + IntPoint newPosition = scrollPosition(); + if (oldPosition != newPosition) { updateLayerPositionsAfterScrolling(); - if (frame().page()->settings().acceleratedCompositingForFixedPositionEnabled()) + if (frame().settings().acceleratedCompositingForFixedPositionEnabled()) updateCompositingLayersAfterScrolling(); - scrollAnimator()->setCurrentPosition(scrollPosition()); - scrollPositionChanged(); + scrollAnimator().setCurrentPosition(newPosition); + scrollPositionChanged(oldPosition, newPosition); } if (visibleContentSizeDidChange) { // Update the scroll-bars to calculate new page-step size. - updateScrollbars(scrollOffset()); + updateScrollbars(scrollPosition()); } - frame().loader().client().didChangeScrollOffset(); + didChangeScrollOffset(); } #endif @@ -1980,25 +2491,92 @@ void FrameView::setViewportConstrainedObjectsNeedLayout() renderer->setNeedsLayout(); } -void FrameView::scrollPositionChangedViaPlatformWidget() +void FrameView::didChangeScrollOffset() +{ + frame().mainFrame().pageOverlayController().didScrollFrame(frame()); + frame().loader().client().didChangeScrollOffset(); +} + +void FrameView::scrollOffsetChangedViaPlatformWidgetImpl(const ScrollOffset& oldOffset, const ScrollOffset& newOffset) { updateLayerPositionsAfterScrolling(); updateCompositingLayersAfterScrolling(); repaintSlowRepaintObjects(); - scrollPositionChanged(); + scrollPositionChanged(scrollPositionFromOffset(oldOffset), scrollPositionFromOffset(newOffset)); } -void FrameView::scrollPositionChanged() +// These scroll positions are affected by zooming. +void FrameView::scrollPositionChanged(const ScrollPosition& oldPosition, const ScrollPosition& newPosition) { - frame().eventHandler().sendScrollEvent(); - frame().eventHandler().dispatchFakeMouseMoveEventSoon(); + Page* page = frame().page(); + Seconds throttlingDelay = page ? page->chrome().client().eventThrottlingDelay() : 0_s; + + if (throttlingDelay == 0_s) { + m_delayedScrollEventTimer.stop(); + sendScrollEvent(); + } else if (!m_delayedScrollEventTimer.isActive()) + m_delayedScrollEventTimer.startOneShot(throttlingDelay.value()); + + if (Document* document = frame().document()) + document->sendWillRevealEdgeEventsIfNeeded(oldPosition, newPosition, visibleContentRect(), contentsSize()); -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { if (renderView->usesCompositing()) renderView->compositor().frameViewDidScroll(); } -#endif + + LOG_WITH_STREAM(Scrolling, stream << "FrameView " << this << " scrollPositionChanged from " << oldPosition << " to " << newPosition << " (scale " << frameScaleFactor() << " )"); + updateLayoutViewport(); + viewportContentsChanged(); +} + +void FrameView::applyRecursivelyWithVisibleRect(const std::function<void (FrameView& frameView, const IntRect& visibleRect)>& apply) +{ + IntRect windowClipRect = this->windowClipRect(); + auto visibleRect = windowToContents(windowClipRect); + apply(*this, visibleRect); + + // Recursive call for subframes. We cache the current FrameView's windowClipRect to avoid recomputing it for every subframe. + SetForScope<IntRect*> windowClipRectCache(m_cachedWindowClipRect, &windowClipRect); + for (Frame* childFrame = frame().tree().firstChild(); childFrame; childFrame = childFrame->tree().nextSibling()) { + if (auto* childView = childFrame->view()) + childView->applyRecursivelyWithVisibleRect(apply); + } +} + +void FrameView::resumeVisibleImageAnimations(const IntRect& visibleRect) +{ + if (visibleRect.isEmpty()) + return; + + if (auto* renderView = frame().contentRenderer()) + renderView->resumePausedImageAnimationsIfNeeded(visibleRect); +} + +void FrameView::updateScriptedAnimationsAndTimersThrottlingState(const IntRect& visibleRect) +{ + if (frame().isMainFrame()) + return; + + auto* document = frame().document(); + if (!document) + return; + + // We don't throttle zero-size or display:none frames because those are usually utility frames. + bool shouldThrottle = visibleRect.isEmpty() && !m_size.isEmpty() && frame().ownerRenderer(); + + if (auto* scriptedAnimationController = document->scriptedAnimationController()) + scriptedAnimationController->setThrottled(shouldThrottle); + + document->setTimerThrottlingEnabled(shouldThrottle); +} + + +void FrameView::resumeVisibleImageAnimationsIncludingSubframes() +{ + applyRecursivelyWithVisibleRect([] (FrameView& frameView, const IntRect& visibleRect) { + frameView.resumeVisibleImageAnimations(visibleRect); + }); } void FrameView::updateLayerPositionsAfterScrolling() @@ -2024,7 +2602,7 @@ bool FrameView::shouldUpdateCompositingLayersAfterScrolling() const if (!page) return true; - if (&page->mainFrame() != &frame()) + if (&page->mainFrame() != m_frame.ptr()) return true; ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator(); @@ -2034,7 +2612,7 @@ bool FrameView::shouldUpdateCompositingLayersAfterScrolling() const if (!scrollingCoordinator->supportsFixedPositionLayers()) return true; - if (scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously()) + if (scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously(*this)) return true; if (inProgrammaticScroll()) @@ -2047,7 +2625,8 @@ bool FrameView::shouldUpdateCompositingLayersAfterScrolling() const void FrameView::updateCompositingLayersAfterScrolling() { -#if USE(ACCELERATED_COMPOSITING) + ASSERT(m_layoutPhase >= InPostLayout || m_layoutPhase == OutsideLayout); + if (!shouldUpdateCompositingLayersAfterScrolling()) return; @@ -2055,7 +2634,6 @@ void FrameView::updateCompositingLayersAfterScrolling() if (RenderView* renderView = this->renderView()) renderView->compositor().updateCompositingLayers(CompositingUpdateOnScroll); } -#endif } bool FrameView::isRubberBandInProgress() const @@ -2067,7 +2645,7 @@ bool FrameView::isRubberBandInProgress() const // ScrollingCoordinator::isRubberBandInProgress(). if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) { - if (!scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously()) + if (!scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously(*this)) return scrollingCoordinator->isRubberBandInProgress(); } } @@ -2080,18 +2658,19 @@ bool FrameView::isRubberBandInProgress() const return false; } -bool FrameView::requestScrollPositionUpdate(const IntPoint& position) +bool FrameView::requestScrollPositionUpdate(const ScrollPosition& position) { + LOG_WITH_STREAM(Scrolling, stream << "FrameView::requestScrollPositionUpdate " << position); + #if ENABLE(ASYNC_SCROLLING) - if (TiledBacking* tiledBacking = this->tiledBacking()) { - IntRect visibleRect = visibleContentRect(); - visibleRect.setLocation(position); - tiledBacking->prepopulateRect(visibleRect); - } + if (TiledBacking* tiledBacking = this->tiledBacking()) + tiledBacking->prepopulateRect(FloatRect(position, visibleContentRect().size())); +#endif +#if ENABLE(ASYNC_SCROLLING) || USE(COORDINATED_GRAPHICS) if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - return scrollingCoordinator->requestScrollPositionUpdate(this, position); + return scrollingCoordinator->requestScrollPositionUpdate(*this, position); } #else UNUSED_PARAM(position); @@ -2102,74 +2681,73 @@ bool FrameView::requestScrollPositionUpdate(const IntPoint& position) HostWindow* FrameView::hostWindow() const { - if (Page* page = frame().page()) - return &page->chrome(); - return 0; + auto* page = frame().page(); + if (!page) + return nullptr; + return &page->chrome(); } -void FrameView::addTrackedRepaintRect(const IntRect& r) +void FrameView::addTrackedRepaintRect(const FloatRect& r) { if (!m_isTrackingRepaints || r.isEmpty()) return; - IntRect repaintRect = r; - repaintRect.move(-scrollOffset()); + FloatRect repaintRect = r; + repaintRect.moveBy(-scrollPosition()); m_trackedRepaintRects.append(repaintRect); } -void FrameView::repaintContentRectangle(const IntRect& r, bool immediate) +void FrameView::repaintContentRectangle(const IntRect& r) { ASSERT(!frame().ownerElement()); - if (!shouldUpdate(immediate)) + if (!shouldUpdate()) return; -#if USE(TILED_BACKING_STORE) - if (frame().tiledBackingStore()) { - frame().tiledBackingStore()->invalidate(r); - return; - } -#endif - ScrollView::repaintContentRectangle(r, immediate); + ScrollView::repaintContentRectangle(r); } -static unsigned countRenderedCharactersInRenderObjectWithThreshold(const RenderObject& renderer, unsigned countSoFar, unsigned threshold) +static unsigned countRenderedCharactersInRenderObjectWithThreshold(const RenderElement& renderer, unsigned threshold) { - // FIXME: Consider writing this using RenderObject::nextInPreOrder() instead of using recursion. - if (renderer.isText()) - countSoFar += toRenderText(renderer).text()->length(); - - for (RenderObject* child = renderer.firstChildSlow(); child; child = child->nextSibling()) { - if (countSoFar >= threshold) - break; - countSoFar = countRenderedCharactersInRenderObjectWithThreshold(*child, countSoFar, threshold); + unsigned count = 0; + for (const RenderObject* descendant = &renderer; descendant; descendant = descendant->nextInPreOrder()) { + if (is<RenderText>(*descendant)) { + count += downcast<RenderText>(*descendant).text()->length(); + if (count >= threshold) + break; + } } - return countSoFar; + return count; } bool FrameView::renderedCharactersExceed(unsigned threshold) { - if (!m_frame->contentRenderer()) + if (!frame().contentRenderer()) return false; - return countRenderedCharactersInRenderObjectWithThreshold(*m_frame->contentRenderer(), 0, threshold) >= threshold; + return countRenderedCharactersInRenderObjectWithThreshold(*frame().contentRenderer(), threshold) >= threshold; } -void FrameView::contentsResized() +void FrameView::availableContentSizeChanged(AvailableSizeChangeReason reason) { - ScrollView::contentsResized(); + if (Document* document = frame().document()) { + // FIXME: Merge this logic with m_setNeedsLayoutWasDeferred and find a more appropriate + // way of handling potential recursive layouts when the viewport is resized to accomodate + // the content but the content always overflows the viewport. See webkit.org/b/165781. + if (!(layoutPhase() == InViewSizeAdjust && useFixedLayout())) + document->updateViewportUnitsOnResize(); + } + + updateLayoutViewport(); setNeedsLayout(); + ScrollView::availableContentSizeChanged(reason); } -void FrameView::fixedLayoutSizeChanged() +bool FrameView::shouldLayoutAfterContentsResized() const { - // Can be triggered before the view is set, see comment in FrameView::visibleContentsResized(). - // An ASSERT is triggered when a view schedules a layout before being attached to a frame. - if (!frame().view()) - return; - ScrollView::fixedLayoutSizeChanged(); + return !useFixedLayout() || useCustomFixedPositionLayoutRect(); } -void FrameView::visibleContentsResized() +void FrameView::updateContentsSize() { // We check to make sure the view is attached to a frame() as this method can // be triggered before the view is attached by Frame::createView(...) setting @@ -2190,44 +2768,134 @@ void FrameView::visibleContentsResized() } #endif - if (!useFixedLayout() && needsLayout()) + if (shouldLayoutAfterContentsResized() && needsLayout()) layout(); -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { if (renderView->usesCompositing()) renderView->compositor().frameViewDidChangeSize(); } -#endif } void FrameView::addedOrRemovedScrollbar() { -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { if (renderView->usesCompositing()) renderView->compositor().frameViewDidAddOrRemoveScrollbars(); } + + updateTiledBackingAdaptiveSizing(); +} + +TiledBacking::Scrollability FrameView::computeScrollability() const +{ + auto* page = frame().page(); + + // Use smaller square tiles if the Window is not active to facilitate app napping. + if (!page || !page->isWindowActive()) + return TiledBacking::HorizontallyScrollable | TiledBacking::VerticallyScrollable; + + bool horizontallyScrollable; + bool verticallyScrollable; + bool clippedByAncestorView = static_cast<bool>(m_viewExposedRect); + +#if PLATFORM(IOS) + if (page) + clippedByAncestorView |= page->enclosedInScrollableAncestorView(); +#endif + + if (delegatesScrolling()) { + IntSize documentSize = contentsSize(); + IntSize visibleSize = this->visibleSize(); + + horizontallyScrollable = clippedByAncestorView || documentSize.width() > visibleSize.width(); + verticallyScrollable = clippedByAncestorView || documentSize.height() > visibleSize.height(); + } else { + horizontallyScrollable = clippedByAncestorView || horizontalScrollbar(); + verticallyScrollable = clippedByAncestorView || verticalScrollbar(); + } + + TiledBacking::Scrollability scrollability = TiledBacking::NotScrollable; + if (horizontallyScrollable) + scrollability = TiledBacking::HorizontallyScrollable; + + if (verticallyScrollable) + scrollability |= TiledBacking::VerticallyScrollable; + + return scrollability; +} + +void FrameView::updateTiledBackingAdaptiveSizing() +{ + auto* tiledBacking = this->tiledBacking(); + if (!tiledBacking) + return; + + tiledBacking->setScrollability(computeScrollability()); +} + +#if PLATFORM(IOS) + +void FrameView::unobscuredContentSizeChanged() +{ + updateTiledBackingAdaptiveSizing(); +} + #endif + +static LayerFlushThrottleState::Flags determineLayerFlushThrottleState(Page& page) +{ + // We only throttle when constantly receiving new data during the inital page load. + if (!page.progress().isMainLoadProgressing()) + return 0; + // Scrolling during page loading disables throttling. + if (page.mainFrame().view()->wasScrolledByUser()) + return 0; + // Disable for image documents so large GIF animations don't get throttled during loading. + auto* document = page.mainFrame().document(); + if (!document || is<ImageDocument>(*document)) + return 0; + return LayerFlushThrottleState::Enabled; } void FrameView::disableLayerFlushThrottlingTemporarilyForInteraction() { -#if USE(ACCELERATED_COMPOSITING) + if (!frame().page()) + return; + auto& page = *frame().page(); + + LayerFlushThrottleState::Flags flags = LayerFlushThrottleState::UserIsInteracting | determineLayerFlushThrottleState(page); + if (page.chrome().client().adjustLayerFlushThrottling(flags)) + return; + if (RenderView* view = renderView()) view->compositor().disableLayerFlushThrottlingTemporarilyForInteraction(); -#endif } -void FrameView::updateLayerFlushThrottlingInAllFrames() +void FrameView::loadProgressingStatusChanged() { -#if USE(ACCELERATED_COMPOSITING) - bool isMainLoadProgressing = frame().page()->progress().isMainLoadProgressing(); - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { + updateLayerFlushThrottling(); + adjustTiledBackingCoverage(); +} + +void FrameView::updateLayerFlushThrottling() +{ + Page* page = frame().page(); + if (!page) + return; + + ASSERT(frame().isMainFrame()); + + LayerFlushThrottleState::Flags flags = determineLayerFlushThrottleState(*page); + + // See if the client is handling throttling. + if (page->chrome().client().adjustLayerFlushThrottling(flags)) + return; + + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) { if (RenderView* renderView = frame->contentRenderer()) - renderView->compositor().setLayerFlushThrottlingEnabled(isMainLoadProgressing); + renderView->compositor().setLayerFlushThrottlingEnabled(flags & LayerFlushThrottleState::Enabled); } -#endif } void FrameView::adjustTiledBackingCoverage() @@ -2235,20 +2903,19 @@ void FrameView::adjustTiledBackingCoverage() if (!m_speculativeTilingEnabled) enableSpeculativeTilingIfNeeded(); -#if USE(ACCELERATED_COMPOSITING) RenderView* renderView = this->renderView(); if (renderView && renderView->layer()->backing()) renderView->layer()->backing()->adjustTiledBackingCoverage(); -#endif #if PLATFORM(IOS) - if (TileCache* tileCache = this->tileCache()) + if (LegacyTileCache* tileCache = legacyTileCache()) tileCache->setSpeculativeTileCreationEnabled(m_speculativeTilingEnabled); #endif } static bool shouldEnableSpeculativeTilingDuringLoading(const FrameView& view) { - return view.isVisuallyNonEmpty() && !view.frame().page()->progress().isMainLoadProgressing(); + Page* page = view.frame().page(); + return page && view.isVisuallyNonEmpty() && !page->progress().isMainLoadProgressing(); } void FrameView::enableSpeculativeTilingIfNeeded() @@ -2267,7 +2934,7 @@ void FrameView::enableSpeculativeTilingIfNeeded() m_speculativeTilingEnableTimer.startOneShot(speculativeTilingEnableDelay); } -void FrameView::speculativeTilingEnableTimerFired(Timer<FrameView>&) +void FrameView::speculativeTilingEnableTimerFired() { if (m_speculativeTilingEnabled) return; @@ -2275,11 +2942,30 @@ void FrameView::speculativeTilingEnableTimerFired(Timer<FrameView>&) adjustTiledBackingCoverage(); } -void FrameView::layoutTimerFired(Timer<FrameView>&) +void FrameView::show() { -#ifdef INSTRUMENT_LAYOUT_SCHEDULING + ScrollView::show(); + + if (frame().isMainFrame()) { + // Turn off speculative tiling for a brief moment after a FrameView appears on screen. + // Note that adjustTiledBackingCoverage() kicks the (500ms) timer to re-enable it. + m_speculativeTilingEnabled = false; + m_wasScrolledByUser = false; + adjustTiledBackingCoverage(); + } +} +void FrameView::convertSubtreeLayoutToFullLayout() +{ + ASSERT(m_layoutRoot); + m_layoutRoot->markContainingBlocksForLayout(ScheduleRelayout::No); + m_layoutRoot = nullptr; +} + +void FrameView::layoutTimerFired() +{ +#if !LOG_DISABLED if (!frame().document()->ownerElement()) - printf("Layout timer fired at %lld\n", frame().document()->elapsedTime().count()); + LOG(Layout, "FrameView %p layout timer fired at %.3fs", this, frame().document()->timeSinceDocumentCreation().value()); #endif layout(); } @@ -2290,33 +2976,32 @@ void FrameView::scheduleRelayout() // too many false assertions. See <rdar://problem/7218118>. ASSERT(frame().view() == this); - if (m_layoutRoot) { - m_layoutRoot->markContainingBlocksForLayout(false); - m_layoutRoot = 0; - } + if (m_layoutRoot) + convertSubtreeLayoutToFullLayout(); if (!m_layoutSchedulingEnabled) return; if (!needsLayout()) return; if (!frame().document()->shouldScheduleLayout()) return; - InspectorInstrumentation::didInvalidateLayout(&frame()); + InspectorInstrumentation::didInvalidateLayout(frame()); // When frame flattening is enabled, the contents of the frame could affect the layout of the parent frames. // Also invalidate parent frame starting from the owner element of this frame. if (frame().ownerRenderer() && isInChildFrameWithFrameFlattening()) frame().ownerRenderer()->setNeedsLayout(MarkContainingBlockChain); - std::chrono::milliseconds delay = frame().document()->minimumLayoutDelay(); - if (m_layoutTimer.isActive() && m_delayedLayout && !delay.count()) + Seconds delay = frame().document()->minimumLayoutDelay(); + if (m_layoutTimer.isActive() && m_delayedLayout && !delay) unscheduleRelayout(); + if (m_layoutTimer.isActive()) return; - m_delayedLayout = delay.count(); + m_delayedLayout = delay.value(); -#ifdef INSTRUMENT_LAYOUT_SCHEDULING +#if !LOG_DISABLED if (!frame().document()->ownerElement()) - printf("Scheduling layout for %d\n", delay); + LOG(Layout, "FrameView %p scheduling layout for %.3fs", this, delay.value()); #endif m_layoutTimer.startOneShot(delay); @@ -2334,23 +3019,26 @@ static bool isObjectAncestorContainerOf(RenderObject* ancestor, RenderObject* de void FrameView::scheduleRelayoutOfSubtree(RenderElement& newRelayoutRoot) { ASSERT(renderView()); - RenderView& renderView = *this->renderView(); + const RenderView& renderView = *this->renderView(); // Try to catch unnecessary work during render tree teardown. - ASSERT(!renderView.documentBeingDestroyed()); + ASSERT(!renderView.renderTreeBeingDestroyed()); ASSERT(frame().view() == this); - if (renderView.needsLayout()) { - newRelayoutRoot.markContainingBlocksForLayout(false); + // When m_layoutRoot is already set, ignore the renderView's needsLayout bit + // since we need to resolve the conflict between the m_layoutRoot and newRelayoutRoot layouts. + if (renderView.needsLayout() && !m_layoutRoot) { + m_layoutRoot = &newRelayoutRoot; + convertSubtreeLayoutToFullLayout(); return; } if (!layoutPending() && m_layoutSchedulingEnabled) { - std::chrono::milliseconds delay = renderView.document().minimumLayoutDelay(); - ASSERT(!newRelayoutRoot.container() || !newRelayoutRoot.container()->needsLayout()); + Seconds delay = renderView.document().minimumLayoutDelay(); + ASSERT(!newRelayoutRoot.container() || is<RenderView>(newRelayoutRoot.container()) || !newRelayoutRoot.container()->needsLayout()); m_layoutRoot = &newRelayoutRoot; - InspectorInstrumentation::didInvalidateLayout(&frame()); - m_delayedLayout = delay.count(); + InspectorInstrumentation::didInvalidateLayout(frame()); + m_delayedLayout = delay.value(); m_layoutTimer.startOneShot(delay); return; } @@ -2359,33 +3047,31 @@ void FrameView::scheduleRelayoutOfSubtree(RenderElement& newRelayoutRoot) return; if (!m_layoutRoot) { - // Just relayout the subtree. - newRelayoutRoot.markContainingBlocksForLayout(false); - InspectorInstrumentation::didInvalidateLayout(&frame()); + // We already have a pending (full) layout. Just mark the subtree for layout. + newRelayoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No); + InspectorInstrumentation::didInvalidateLayout(frame()); return; } if (isObjectAncestorContainerOf(m_layoutRoot, &newRelayoutRoot)) { // Keep the current root. - newRelayoutRoot.markContainingBlocksForLayout(false, m_layoutRoot); - ASSERT(!m_layoutRoot->container() || !m_layoutRoot->container()->needsLayout()); + newRelayoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No, m_layoutRoot); + ASSERT(!m_layoutRoot->container() || is<RenderView>(m_layoutRoot->container()) || !m_layoutRoot->container()->needsLayout()); return; } if (isObjectAncestorContainerOf(&newRelayoutRoot, m_layoutRoot)) { // Re-root at newRelayoutRoot. - m_layoutRoot->markContainingBlocksForLayout(false, &newRelayoutRoot); + m_layoutRoot->markContainingBlocksForLayout(ScheduleRelayout::No, &newRelayoutRoot); m_layoutRoot = &newRelayoutRoot; - ASSERT(!m_layoutRoot->container() || !m_layoutRoot->container()->needsLayout()); - InspectorInstrumentation::didInvalidateLayout(&frame()); + ASSERT(!m_layoutRoot->container() || is<RenderView>(m_layoutRoot->container()) || !m_layoutRoot->container()->needsLayout()); + InspectorInstrumentation::didInvalidateLayout(frame()); return; } - - // Just do a full relayout. - m_layoutRoot->markContainingBlocksForLayout(false); - m_layoutRoot = 0; - newRelayoutRoot.markContainingBlocksForLayout(false); - InspectorInstrumentation::didInvalidateLayout(&frame()); + // Two disjoint subtrees need layout. Mark both of them and issue a full layout instead. + convertSubtreeLayoutToFullLayout(); + newRelayoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No); + InspectorInstrumentation::didInvalidateLayout(frame()); } bool FrameView::layoutPending() const @@ -2393,6 +3079,25 @@ bool FrameView::layoutPending() const return m_layoutTimer.isActive(); } +bool FrameView::needsStyleRecalcOrLayout(bool includeSubframes) const +{ + if (frame().document() && frame().document()->childNeedsStyleRecalc()) + return true; + + if (needsLayout()) + return true; + + if (!includeSubframes) + return false; + + for (auto& frameView : renderedChildFrameViews()) { + if (frameView->needsStyleRecalcOrLayout()) + return true; + } + + return false; +} + bool FrameView::needsLayout() const { // This can return true in cases where the document does not have a body yet. @@ -2402,50 +3107,57 @@ bool FrameView::needsLayout() const return layoutPending() || (renderView && renderView->needsLayout()) || m_layoutRoot - || (m_deferSetNeedsLayouts && m_setNeedsLayoutWasDeferred); + || (m_deferSetNeedsLayoutCount && m_setNeedsLayoutWasDeferred); } void FrameView::setNeedsLayout() { - if (m_deferSetNeedsLayouts) { + if (m_deferSetNeedsLayoutCount) { m_setNeedsLayoutWasDeferred = true; return; } - if (RenderView* renderView = this->renderView()) + if (auto* renderView = this->renderView()) { + ASSERT(!renderView->inHitTesting()); renderView->setNeedsLayout(); + } } void FrameView::unscheduleRelayout() { + if (m_postLayoutTasksTimer.isActive()) + m_postLayoutTasksTimer.stop(); + if (!m_layoutTimer.isActive()) return; -#ifdef INSTRUMENT_LAYOUT_SCHEDULING +#if !LOG_DISABLED if (!frame().document()->ownerElement()) - printf("Layout timer unscheduled at %d\n", frame().document()->elapsedTime()); + LOG(Layout, "FrameView %p layout timer unscheduled at %.3fs", this, frame().document()->timeSinceDocumentCreation().value()); #endif m_layoutTimer.stop(); m_delayedLayout = false; } -#if ENABLE(REQUEST_ANIMATION_FRAME) -void FrameView::serviceScriptedAnimations(double monotonicAnimationStartTime) +void FrameView::serviceScriptedAnimations() { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext()) { + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext()) { frame->view()->serviceScrollAnimations(); frame->animation().serviceAnimations(); } + if (!frame().document() || !frame().document()->domWindow()) + return; + Vector<RefPtr<Document>> documents; - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext()) + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext()) documents.append(frame->document()); - for (size_t i = 0; i < documents.size(); ++i) - documents[i]->serviceScriptedAnimations(monotonicAnimationStartTime); + double timestamp = frame().document()->domWindow()->nowTimestamp(); + for (auto& document : documents) + document->serviceScriptedAnimations(timestamp); } -#endif bool FrameView::isTransparent() const { @@ -2454,12 +3166,24 @@ bool FrameView::isTransparent() const void FrameView::setTransparent(bool isTransparent) { + if (m_isTransparent == isTransparent) + return; + m_isTransparent = isTransparent; + + // setTransparent can be called in the window between FrameView initialization + // and switching in the new Document; this means that the RenderView that we + // retrieve is actually attached to the previous Document, which is going away, + // and must not update compositing layers. + if (!isViewForDocumentInFrame()) + return; + + renderView()->compositor().rootBackgroundTransparencyChanged(); } bool FrameView::hasOpaqueBackground() const { - return !m_isTransparent && !m_baseBackgroundColor.hasAlpha(); + return !m_isTransparent && m_baseBackgroundColor.isOpaque(); } Color FrameView::baseBackgroundColor() const @@ -2469,17 +3193,25 @@ Color FrameView::baseBackgroundColor() const void FrameView::setBaseBackgroundColor(const Color& backgroundColor) { + bool wasOpaque = m_baseBackgroundColor.isOpaque(); + if (!backgroundColor.isValid()) m_baseBackgroundColor = Color::white; else m_baseBackgroundColor = backgroundColor; + if (!isViewForDocumentInFrame()) + return; + recalculateScrollbarOverlayStyle(); + + if (m_baseBackgroundColor.isOpaque() != wasOpaque) + renderView()->compositor().rootBackgroundTransparencyChanged(); } void FrameView::updateBackgroundRecursively(const Color& backgroundColor, bool transparent) { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) { if (FrameView* view = frame->view()) { view->setTransparent(transparent); view->setBaseBackgroundColor(backgroundColor); @@ -2487,9 +3219,8 @@ void FrameView::updateBackgroundRecursively(const Color& backgroundColor, bool t } } -bool FrameView::hasExtendedBackground() const +bool FrameView::hasExtendedBackgroundRectForPainting() const { -#if USE(ACCELERATED_COMPOSITING) if (!frame().settings().backgroundShouldExtendBeyondPage()) return false; @@ -2498,31 +3229,64 @@ bool FrameView::hasExtendedBackground() const return false; return tiledBacking->hasMargins(); -#else - return false; -#endif } -IntRect FrameView::extendedBackgroundRect() const +void FrameView::updateExtendBackgroundIfNecessary() { -#if USE(ACCELERATED_COMPOSITING) - TiledBacking* tiledBacking = this->tiledBacking(); - if (!tiledBacking) - return IntRect(); + ExtendedBackgroundMode mode = calculateExtendedBackgroundMode(); + if (mode == ExtendedBackgroundModeNone) + return; - return tiledBacking->bounds(); + updateTilesForExtendedBackgroundMode(mode); +} + +FrameView::ExtendedBackgroundMode FrameView::calculateExtendedBackgroundMode() const +{ + // Just because Settings::backgroundShouldExtendBeyondPage() is true does not necessarily mean + // that the background rect needs to be extended for painting. Simple backgrounds can be extended + // just with RenderLayerCompositor::setRootExtendedBackgroundColor(). More complicated backgrounds, + // such as images, require extending the background rect to continue painting into the extended + // region. This function finds out if it is necessary to extend the background rect for painting. + +#if PLATFORM(IOS) + // <rdar://problem/16201373> + return ExtendedBackgroundModeNone; #else - RenderView* renderView = this->renderView(); - if (!renderView) - return IntRect(); + if (!frame().settings().backgroundShouldExtendBeyondPage()) + return ExtendedBackgroundModeNone; - return renderView->unscaledDocumentRect(); + if (!frame().isMainFrame()) + return ExtendedBackgroundModeNone; + + Document* document = frame().document(); + if (!document) + return ExtendedBackgroundModeNone; + + if (!renderView()) + return ExtendedBackgroundModeNone; + + auto* rootBackgroundRenderer = renderView()->rendererForRootBackground(); + if (!rootBackgroundRenderer) + return ExtendedBackgroundModeNone; + + if (!rootBackgroundRenderer->style().hasBackgroundImage()) + return ExtendedBackgroundModeNone; + + ExtendedBackgroundMode mode = ExtendedBackgroundModeNone; + if (rootBackgroundRenderer->style().backgroundRepeatX() == RepeatFill) + mode |= ExtendedBackgroundModeHorizontal; + if (rootBackgroundRenderer->style().backgroundRepeatY() == RepeatFill) + mode |= ExtendedBackgroundModeVertical; + + return mode; #endif } -#if USE(ACCELERATED_COMPOSITING) -void FrameView::setBackgroundExtendsBeyondPage(bool extendBackground) +void FrameView::updateTilesForExtendedBackgroundMode(ExtendedBackgroundMode mode) { + if (!frame().settings().backgroundShouldExtendBeyondPage()) + return; + RenderView* renderView = this->renderView(); if (!renderView) return; @@ -2531,9 +3295,41 @@ void FrameView::setBackgroundExtendsBeyondPage(bool extendBackground) if (!backing) return; - backing->setTiledBackingHasMargins(extendBackground); + TiledBacking* tiledBacking = backing->graphicsLayer()->tiledBacking(); + if (!tiledBacking) + return; + + ExtendedBackgroundMode existingMode = ExtendedBackgroundModeNone; + if (tiledBacking->hasVerticalMargins()) + existingMode |= ExtendedBackgroundModeVertical; + if (tiledBacking->hasHorizontalMargins()) + existingMode |= ExtendedBackgroundModeHorizontal; + + if (existingMode == mode) + return; + + renderView->compositor().setRootExtendedBackgroundColor(mode == ExtendedBackgroundModeAll ? Color() : documentBackgroundColor()); + backing->setTiledBackingHasMargins(mode & ExtendedBackgroundModeHorizontal, mode & ExtendedBackgroundModeVertical); +} + +IntRect FrameView::extendedBackgroundRectForPainting() const +{ + TiledBacking* tiledBacking = this->tiledBacking(); + if (!tiledBacking) + return IntRect(); + + RenderView* renderView = this->renderView(); + if (!renderView) + return IntRect(); + + LayoutRect extendedRect = renderView->unextendedBackgroundRect(); + if (!tiledBacking->hasMargins()) + return snappedIntRect(extendedRect); + + extendedRect.moveBy(LayoutPoint(-tiledBacking->leftMarginWidth(), -tiledBacking->topMarginHeight())); + extendedRect.expand(LayoutSize(tiledBacking->leftMarginWidth() + tiledBacking->rightMarginWidth(), tiledBacking->topMarginHeight() + tiledBacking->bottomMarginHeight())); + return snappedIntRect(extendedRect); } -#endif bool FrameView::shouldUpdateWhileOffscreen() const { @@ -2545,16 +3341,16 @@ void FrameView::setShouldUpdateWhileOffscreen(bool shouldUpdateWhileOffscreen) m_shouldUpdateWhileOffscreen = shouldUpdateWhileOffscreen; } -bool FrameView::shouldUpdate(bool immediateRequested) const +bool FrameView::shouldUpdate() const { - if (!immediateRequested && isOffscreen() && !shouldUpdateWhileOffscreen()) + if (isOffscreen() && !shouldUpdateWhileOffscreen()) return false; return true; } void FrameView::scrollToAnchor() { - RefPtr<Node> anchorNode = m_maintainScrollPositionAnchor; + RefPtr<ContainerNode> anchorNode = m_maintainScrollPositionAnchor; if (!anchorNode) return; @@ -2562,12 +3358,18 @@ void FrameView::scrollToAnchor() return; LayoutRect rect; - if (anchorNode != frame().document()) - rect = anchorNode->boundingBox(); + bool insideFixed = false; + if (anchorNode != frame().document() && anchorNode->renderer()) + rect = anchorNode->renderer()->absoluteAnchorRect(&insideFixed); // Scroll nested layers and frames to reveal the anchor. // Align to the top and to the closest side (this matches other browsers). - anchorNode->renderer()->scrollRectToVisible(rect, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways); + if (anchorNode->renderer()->style().isHorizontalWritingMode()) + anchorNode->renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, rect, insideFixed, ScrollAlignment::alignToEdgeIfNeeded, ScrollAlignment::alignTopAlways); + else if (anchorNode->renderer()->style().isFlippedBlocksWritingMode()) + anchorNode->renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, rect, insideFixed, ScrollAlignment::alignRightAlways, ScrollAlignment::alignToEdgeIfNeeded); + else + anchorNode->renderer()->scrollRectToVisible(SelectionRevealMode::Reveal, rect, insideFixed, ScrollAlignment::alignLeftAlways, ScrollAlignment::alignToEdgeIfNeeded); if (AXObjectCache* cache = frame().document()->existingAXObjectCache()) cache->handleScrolledToAnchor(anchorNode.get()); @@ -2585,8 +3387,8 @@ void FrameView::updateEmbeddedObject(RenderEmbeddedObject& embeddedObject) HTMLFrameOwnerElement& element = embeddedObject.frameOwnerElement(); if (embeddedObject.isSnapshottedPlugIn()) { - if (isHTMLObjectElement(element) || isHTMLEmbedElement(element)) { - HTMLPlugInImageElement& pluginElement = toHTMLPlugInImageElement(element); + if (is<HTMLObjectElement>(element) || is<HTMLEmbedElement>(element)) { + HTMLPlugInImageElement& pluginElement = downcast<HTMLPlugInImageElement>(element); pluginElement.checkSnapshotStatus(); } return; @@ -2596,29 +3398,23 @@ void FrameView::updateEmbeddedObject(RenderEmbeddedObject& embeddedObject) // FIXME: This could turn into a real virtual dispatch if we defined // updateWidget(PluginCreationOption) on HTMLElement. - if (isHTMLObjectElement(element) || isHTMLEmbedElement(element) || isHTMLAppletElement(element)) { - HTMLPlugInImageElement& pluginElement = toHTMLPlugInImageElement(element); + if (is<HTMLPlugInImageElement>(element)) { + HTMLPlugInImageElement& pluginElement = downcast<HTMLPlugInImageElement>(element); if (pluginElement.needsCheckForSizeChange()) { pluginElement.checkSnapshotStatus(); return; } if (pluginElement.needsWidgetUpdate()) - pluginElement.updateWidget(CreateAnyWidgetType); - } - - // FIXME: It is not clear that Media elements need or want this updateWidget() call. -#if ENABLE(PLUGIN_PROXY_FOR_VIDEO) - else if (element.isMediaElement()) - toHTMLMediaElement(element).updateWidget(CreateAnyWidgetType); -#endif - else + pluginElement.updateWidget(CreatePlugins::Yes); + } else ASSERT_NOT_REACHED(); // It's possible the renderer was destroyed below updateWidget() since loading a plugin may execute arbitrary JavaScript. if (!weakRenderer) return; - embeddedObject.updateWidgetPosition(); + auto ignoreWidgetState = embeddedObject.updateWidgetPosition(); + UNUSED_PARAM(ignoreWidgetState); } bool FrameView::updateEmbeddedObjects() @@ -2642,9 +3438,9 @@ bool FrameView::updateEmbeddedObjects() return m_embeddedObjectsToUpdate->isEmpty(); } -void FrameView::updateEmbeddedObjectsTimerFired(Timer<FrameView>*) +void FrameView::updateEmbeddedObjectsTimerFired() { - RefPtr<FrameView> protect(this); + RefPtr<FrameView> protectedThis(this); m_updateEmbeddedObjectsTimer.stop(); for (unsigned i = 0; i < maxUpdateEmbeddedObjectsIterations; i++) { if (updateEmbeddedObjects()) @@ -2657,57 +3453,55 @@ void FrameView::flushAnyPendingPostLayoutTasks() if (m_postLayoutTasksTimer.isActive()) performPostLayoutTasks(); if (m_updateEmbeddedObjectsTimer.isActive()) - updateEmbeddedObjectsTimerFired(nullptr); + updateEmbeddedObjectsTimerFired(); +} + +void FrameView::queuePostLayoutCallback(Function<void()>&& callback) +{ + m_postLayoutCallbackQueue.append(WTFMove(callback)); +} + +void FrameView::flushPostLayoutTasksQueue() +{ + if (m_nestedLayoutCount > 1) + return; + + if (!m_postLayoutCallbackQueue.size()) + return; + + Vector<Function<void()>> queue = WTFMove(m_postLayoutCallbackQueue); + for (auto& task : queue) + task(); } void FrameView::performPostLayoutTasks() { + LOG(Layout, "FrameView %p performPostLayoutTasks", this); + // FIXME: We should not run any JavaScript code in this function. m_postLayoutTasksTimer.stop(); - frame().selection().setCaretRectNeedsUpdate(); - frame().selection().updateAppearance(); - - LayoutMilestones requestedMilestones = 0; - LayoutMilestones milestonesAchieved = 0; - Page* page = frame().page(); - if (page) - requestedMilestones = page->requestedLayoutMilestones(); + frame().selection().updateAppearanceAfterLayout(); - if (m_nestedLayoutCount <= 1 && frame().document()->documentElement()) { - if (m_firstLayoutCallbackPending) { - m_firstLayoutCallbackPending = false; - frame().loader().didFirstLayout(); - if (requestedMilestones & DidFirstLayout) - milestonesAchieved |= DidFirstLayout; - if (frame().isMainFrame()) - page->startCountingRelevantRepaintedObjects(); - } - updateIsVisuallyNonEmpty(); + flushPostLayoutTasksQueue(); - // If the layout was done with pending sheets, we are not in fact visually non-empty yet. - if (m_isVisuallyNonEmpty && !frame().document()->didLayoutWithPendingStylesheets() && m_firstVisuallyNonEmptyLayoutCallbackPending) { - m_firstVisuallyNonEmptyLayoutCallbackPending = false; - if (requestedMilestones & DidFirstVisuallyNonEmptyLayout) - milestonesAchieved |= DidFirstVisuallyNonEmptyLayout; - } - } + if (m_nestedLayoutCount <= 1 && frame().document()->documentElement()) + fireLayoutRelatedMilestonesIfNeeded(); #if PLATFORM(IOS) // Only send layout-related delegate callbacks synchronously for the main frame to // avoid re-entering layout for the main frame while delivering a layout-related delegate // callback for a subframe. - if (frame().isMainFrame()) - page->chrome().client().didLayout(); + if (frame().isMainFrame()) { + if (Page* page = frame().page()) + page->chrome().client().didLayout(); + } #endif - if (milestonesAchieved && frame().isMainFrame()) - frame().loader().didLayout(milestonesAchieved); - #if ENABLE(FONT_LOAD_EVENTS) if (RuntimeEnabledFeatures::sharedFeatures().fontLoadEventsEnabled()) - frame().document()->fontloader()->didLayout(); + frame().document()->fonts()->didLayout(); #endif // FIXME: We should consider adding DidLayout as a LayoutMilestone. That would let us merge this @@ -2715,51 +3509,73 @@ void FrameView::performPostLayoutTasks() frame().loader().client().dispatchDidLayout(); updateWidgetPositions(); - + +#if ENABLE(CSS_SCROLL_SNAP) + updateSnapOffsets(); +#endif + // layout() protects FrameView, but it still can get destroyed when updateEmbeddedObjects() // is called through the post layout timer. - Ref<FrameView> protect(*this); + Ref<FrameView> protectedThis(*this); m_updateEmbeddedObjectsTimer.startOneShot(0); - if (page) { - if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - scrollingCoordinator->frameViewLayoutUpdated(this); + if (auto* page = frame().page()) { + if (auto* scrollingCoordinator = page->scrollingCoordinator()) + scrollingCoordinator->frameViewLayoutUpdated(*this); } -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) { if (renderView->usesCompositing()) renderView->compositor().frameViewDidLayout(); } -#endif scrollToAnchor(); sendResizeEventIfNeeded(); + + updateLayoutViewport(); + viewportContentsChanged(); + + updateScrollSnapState(); + + if (AXObjectCache* cache = frame().document()->existingAXObjectCache()) + cache->performDeferredIsIgnoredChange(); +} + +IntSize FrameView::sizeForResizeEvent() const +{ +#if PLATFORM(IOS) + if (m_useCustomSizeForResizeEvent) + return m_customSizeForResizeEvent; +#endif + if (useFixedLayout() && !fixedLayoutSize().isEmpty() && delegatesScrolling()) + return fixedLayoutSize(); + return visibleContentRectIncludingScrollbars().size(); } void FrameView::sendResizeEventIfNeeded() { + if (isInRenderTreeLayout() || needsLayout()) + return; + RenderView* renderView = this->renderView(); if (!renderView || renderView->printing()) return; + if (frame().page() && frame().page()->chrome().client().isSVGImageChromeClient()) return; - IntSize currentSize; - if (useFixedLayout() && !fixedLayoutSize().isEmpty() && delegatesScrolling()) - currentSize = fixedLayoutSize(); - else - currentSize = visibleContentRectIncludingScrollbars().size(); - + IntSize currentSize = sizeForResizeEvent(); float currentZoomFactor = renderView->style().zoom(); - bool shouldSendResizeEvent = !m_firstLayout && (currentSize != m_lastViewportSize || currentZoomFactor != m_lastZoomFactor); + + if (currentSize == m_lastViewportSize && currentZoomFactor == m_lastZoomFactor) + return; m_lastViewportSize = currentSize; m_lastZoomFactor = currentZoomFactor; - if (!shouldSendResizeEvent) + if (m_firstLayout) return; #if PLATFORM(IOS) @@ -2773,22 +3589,24 @@ void FrameView::sendResizeEventIfNeeded() #endif bool isMainFrame = frame().isMainFrame(); - bool canSendResizeEventSynchronously = !m_shouldAutoSize && isMainFrame && !isInLayout(); + bool canSendResizeEventSynchronously = isMainFrame && !m_shouldAutoSize; - // If we resized during layout, queue up a resize event for later, otherwise fire it right away. - RefPtr<Event> resizeEvent = Event::create(eventNames().resizeEvent, false, false); + Ref<Event> resizeEvent = Event::create(eventNames().resizeEvent, false, false); if (canSendResizeEventSynchronously) - frame().document()->dispatchWindowEvent(resizeEvent.release(), frame().document()->domWindow()); - else - frame().document()->enqueueWindowEvent(resizeEvent.release()); + frame().document()->dispatchWindowEvent(resizeEvent); + else { + // FIXME: Queueing this event for an unpredictable time in the future seems + // intrinsically racy. By the time this resize event fires, the frame might + // be resized again, so we could end up with two resize events for the same size. + frame().document()->enqueueWindowEvent(WTFMove(resizeEvent)); + } -#if ENABLE(INSPECTOR) - Page* page = frame().page(); if (InspectorInstrumentation::hasFrontends() && isMainFrame) { - if (InspectorClient* inspectorClient = page ? page->inspectorController().inspectorClient() : nullptr) - inspectorClient->didResizeMainFrame(&frame()); + if (Page* page = frame().page()) { + if (InspectorClient* inspectorClient = page->inspectorController().inspectorClient()) + inspectorClient->didResizeMainFrame(&frame()); + } } -#endif } void FrameView::willStartLiveResize() @@ -2803,11 +3621,6 @@ void FrameView::willEndLiveResize() adjustTiledBackingCoverage(); } -void FrameView::postLayoutTimerFired(Timer<FrameView>&) -{ - performPostLayoutTasks(); -} - void FrameView::autoSizeIfEnabled() { if (!m_shouldAutoSize) @@ -2816,7 +3629,9 @@ void FrameView::autoSizeIfEnabled() if (m_inAutoSize) return; - TemporaryChange<bool> changeInAutoSize(m_inAutoSize, true); + LOG(Layout, "FrameView %p autoSizeIfEnabled", this); + + SetForScope<bool> changeInAutoSize(m_inAutoSize, true); Document* document = frame().document(); if (!document) @@ -2827,6 +3642,8 @@ void FrameView::autoSizeIfEnabled() if (!documentView || !documentElement) return; + if (m_layoutRoot) + convertSubtreeLayoutToFullLayout(); // Start from the minimum size and allow it to grow. resize(m_minAutoSize.width(), m_minAutoSize.height()); @@ -2849,8 +3666,7 @@ void FrameView::autoSizeIfEnabled() RefPtr<Scrollbar> localHorizontalScrollbar = horizontalScrollbar(); if (!localHorizontalScrollbar) localHorizontalScrollbar = createScrollbar(HorizontalScrollbar); - if (!localHorizontalScrollbar->isOverlayScrollbar()) - newSize.setHeight(newSize.height() + localHorizontalScrollbar->height()); + newSize.expand(0, localHorizontalScrollbar->occupiedHeight()); // Don't bother checking for a vertical scrollbar because the width is at // already greater the maximum. @@ -2858,8 +3674,7 @@ void FrameView::autoSizeIfEnabled() RefPtr<Scrollbar> localVerticalScrollbar = verticalScrollbar(); if (!localVerticalScrollbar) localVerticalScrollbar = createScrollbar(VerticalScrollbar); - if (!localVerticalScrollbar->isOverlayScrollbar()) - newSize.setWidth(newSize.width() + localVerticalScrollbar->width()); + newSize.expand(localVerticalScrollbar->occupiedWidth(), 0); // Don't bother checking for a horizontal scrollbar because the height is // already greater the maximum. @@ -2889,7 +3704,10 @@ void FrameView::autoSizeIfEnabled() && !frame().loader().isComplete() && (newSize.height() < size.height() || newSize.width() < size.width())) break; - resize(newSize.width(), newSize.height()); + // The first time around, resize to the minimum height again; otherwise, + // on pages (e.g. quirks mode) where the body/document resize to the view size, + // we'll end up not shrinking back down after resizing to the computed preferred width. + resize(newSize.width(), i ? newSize.height() : m_minAutoSize.height()); // Force the scrollbar state to avoid the scrollbar code adding them and causing them to be needed. For example, // a vertical scrollbar may cause text to wrap and thus increase the height (which is the only reason the scollbar is needed). setVerticalScrollbarLock(false); @@ -2897,6 +3715,9 @@ void FrameView::autoSizeIfEnabled() setScrollbarModes(horizonalScrollbarMode, verticalScrollbarMode, true, true); } + // All the resizing above may have invalidated style (for example if viewport units are being used). + document->updateStyleIfNeeded(); + m_autoSizeContentSize = contentsSize(); if (m_autoSizeFixedMinimumHeight) { @@ -2917,9 +3738,37 @@ void FrameView::setAutoSizeFixedMinimumHeight(int fixedMinimumHeight) setNeedsLayout(); } +RenderElement* FrameView::viewportRenderer() const +{ + if (m_viewportRendererType == ViewportRendererType::None) + return nullptr; + + auto* document = frame().document(); + if (!document) + return nullptr; + + if (m_viewportRendererType == ViewportRendererType::Document) { + auto* documentElement = document->documentElement(); + if (!documentElement) + return nullptr; + return documentElement->renderer(); + } + + if (m_viewportRendererType == ViewportRendererType::Body) { + auto* body = document->body(); + if (!body) + return nullptr; + return body->renderer(); + } + + ASSERT_NOT_REACHED(); + return nullptr; +} + void FrameView::updateOverflowStatus(bool horizontalOverflow, bool verticalOverflow) { - if (!m_viewportRenderer) + auto* viewportRenderer = this->viewportRenderer(); + if (!viewportRenderer) return; if (m_overflowStatusDirty) { @@ -2936,13 +3785,12 @@ void FrameView::updateOverflowStatus(bool horizontalOverflow, bool verticalOverf m_horizontalOverflow = horizontalOverflow; m_verticalOverflow = verticalOverflow; - RefPtr<OverflowEvent> overflowEvent = OverflowEvent::create(horizontalOverflowChanged, horizontalOverflow, + Ref<OverflowEvent> overflowEvent = OverflowEvent::create(horizontalOverflowChanged, horizontalOverflow, verticalOverflowChanged, verticalOverflow); - overflowEvent->setTarget(m_viewportRenderer->element()); + overflowEvent->setTarget(viewportRenderer->element()); - frame().document()->enqueueOverflowEvent(overflowEvent.release()); + frame().document()->enqueueOverflowEvent(WTFMove(overflowEvent)); } - } const Pagination& FrameView::pagination() const @@ -2950,8 +3798,10 @@ const Pagination& FrameView::pagination() const if (m_pagination != Pagination()) return m_pagination; - if (frame().isMainFrame()) - return frame().page()->pagination(); + if (frame().isMainFrame()) { + if (Page* page = frame().page()) + return page->pagination(); + } return m_pagination; } @@ -2963,22 +3813,22 @@ void FrameView::setPagination(const Pagination& pagination) m_pagination = pagination; - frame().document()->styleResolverChanged(DeferRecalcStyle); + frame().document()->styleScope().didChangeStyleSheetEnvironment(); } -IntRect FrameView::windowClipRect(bool clipToContents) const +IntRect FrameView::windowClipRect() const { ASSERT(frame().view() == this); + if (m_cachedWindowClipRect) + return *m_cachedWindowClipRect; + if (paintsEntireContents()) - return IntRect(IntPoint(), totalContentsSize()); + return contentsToWindow(IntRect(IntPoint(), totalContentsSize())); // Set our clip rect to be our contents. - IntRect clipRect; - if (clipToContents) - clipRect = contentsToWindow(visibleContentRect(LegacyIOSDocumentVisibleRect)); - else - clipRect = contentsToWindow(visibleContentRectIncludingScrollbars(LegacyIOSDocumentVisibleRect)); + IntRect clipRect = contentsToWindow(visibleContentRect(LegacyIOSDocumentVisibleRect)); + if (!frame().ownerElement()) return clipRect; @@ -3004,9 +3854,9 @@ IntRect FrameView::windowClipRectForFrameOwner(const HTMLFrameOwnerElement* owne // Apply the clip from the layer. IntRect clipRect; if (clipToLayerContents) - clipRect = pixelSnappedIntRect(enclosingLayer->childrenClipRect()); + clipRect = snappedIntRect(enclosingLayer->childrenClipRect()); else - clipRect = pixelSnappedIntRect(enclosingLayer->selfClipRect()); + clipRect = snappedIntRect(enclosingLayer->selfClipRect()); clipRect = contentsToWindow(clipRect); return intersection(clipRect, windowClipRect()); } @@ -3017,46 +3867,79 @@ bool FrameView::isActive() const return page && page->focusController().isActive(); } -bool FrameView::updatesScrollLayerPositionOnMainThread() const +bool FrameView::forceUpdateScrollbarsOnMainThreadForPerformanceTesting() const { - if (Page* page = frame().page()) { - if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) - return scrollingCoordinator->shouldUpdateScrollLayerPositionSynchronously(); - } + Page* page = frame().page(); + return page && page->settings().forceUpdateScrollbarsOnMainThreadForPerformanceTesting(); +} - return true; +void FrameView::scrollTo(const ScrollPosition& newPosition) +{ + IntPoint oldPosition = scrollPosition(); + ScrollView::scrollTo(newPosition); + if (oldPosition != scrollPosition()) + scrollPositionChanged(oldPosition, scrollPosition()); + + didChangeScrollOffset(); } -void FrameView::scrollTo(const IntSize& newOffset) +float FrameView::adjustScrollStepForFixedContent(float step, ScrollbarOrientation orientation, ScrollGranularity granularity) { - LayoutSize offset = scrollOffset(); - ScrollView::scrollTo(newOffset); - if (offset != scrollOffset()) - scrollPositionChanged(); - frame().loader().client().didChangeScrollOffset(); + if (granularity != ScrollByPage || orientation == HorizontalScrollbar) + return step; + + TrackedRendererListHashSet* positionedObjects = nullptr; + if (RenderView* root = frame().contentRenderer()) { + if (!root->hasPositionedObjects()) + return step; + positionedObjects = root->positionedObjects(); + } + + FloatRect unobscuredContentRect = this->unobscuredContentRect(); + float topObscuredArea = 0; + float bottomObscuredArea = 0; + for (const auto& positionedObject : *positionedObjects) { + const RenderStyle& style = positionedObject->style(); + if (style.position() != FixedPosition || style.visibility() == HIDDEN || !style.opacity()) + continue; + + FloatQuad contentQuad = positionedObject->absoluteContentQuad(); + if (!contentQuad.isRectilinear()) + continue; + + FloatRect contentBoundingBox = contentQuad.boundingBox(); + FloatRect fixedRectInView = intersection(unobscuredContentRect, contentBoundingBox); + + if (fixedRectInView.width() < unobscuredContentRect.width()) + continue; + + if (fixedRectInView.y() == unobscuredContentRect.y()) + topObscuredArea = std::max(topObscuredArea, fixedRectInView.height()); + else if (fixedRectInView.maxY() == unobscuredContentRect.maxY()) + bottomObscuredArea = std::max(bottomObscuredArea, fixedRectInView.height()); + } + + return Scrollbar::pageStep(unobscuredContentRect.height(), unobscuredContentRect.height() - topObscuredArea - bottomObscuredArea); } -void FrameView::invalidateScrollbarRect(Scrollbar* scrollbar, const IntRect& rect) +void FrameView::invalidateScrollbarRect(Scrollbar& scrollbar, const IntRect& rect) { // Add in our offset within the FrameView. IntRect dirtyRect = rect; - dirtyRect.moveBy(scrollbar->location()); + dirtyRect.moveBy(scrollbar.location()); invalidateRect(dirtyRect); } -IntRect FrameView::windowResizerRect() const -{ - if (Page* page = frame().page()) - return page->chrome().windowResizerRect(); - return IntRect(); -} - float FrameView::visibleContentScaleFactor() const { if (!frame().isMainFrame() || !frame().settings().delegatesPageScaling()) return 1; - return frame().page()->pageScaleFactor(); + Page* page = frame().page(); + if (!page) + return 1; + + return page->pageScaleFactor(); } void FrameView::setVisibleScrollerThumbRect(const IntRect& scrollerThumb) @@ -3064,16 +3947,17 @@ void FrameView::setVisibleScrollerThumbRect(const IntRect& scrollerThumb) if (!frame().isMainFrame()) return; - frame().page()->chrome().client().notifyScrollerThumbIsVisibleInRect(scrollerThumb); + if (Page* page = frame().page()) + page->chrome().client().notifyScrollerThumbIsVisibleInRect(scrollerThumb); } ScrollableArea* FrameView::enclosingScrollableArea() const { // FIXME: Walk up the frame tree and look for a scrollable parent frame or RenderLayer. - return 0; + return nullptr; } -IntRect FrameView::scrollableAreaBoundingBox() const +IntRect FrameView::scrollableAreaBoundingBox(bool*) const { RenderWidget* ownerRenderer = frame().ownerRenderer(); if (!ownerRenderer) @@ -3082,7 +3966,7 @@ IntRect FrameView::scrollableAreaBoundingBox() const return ownerRenderer->absoluteContentQuad().enclosingBoundingBox(); } -bool FrameView::isScrollable() +bool FrameView::isScrollable(Scrollability definitionOfScrollable) { // Check for: // 1) If there an actual overflow. @@ -3090,11 +3974,18 @@ bool FrameView::isScrollable() // 3) overflow{-x,-y}: hidden; // 4) scrolling: no; + bool requiresActualOverflowToBeConsideredScrollable = !frame().isMainFrame() || definitionOfScrollable != Scrollability::ScrollableOrRubberbandable; +#if !ENABLE(RUBBER_BANDING) + requiresActualOverflowToBeConsideredScrollable = true; +#endif + // Covers #1 - IntSize totalContentsSize = this->totalContentsSize(); - IntSize visibleContentSize = visibleContentRect(LegacyIOSDocumentVisibleRect).size(); - if ((totalContentsSize.height() <= visibleContentSize.height() && totalContentsSize.width() <= visibleContentSize.width())) - return false; + if (requiresActualOverflowToBeConsideredScrollable) { + IntSize totalContentsSize = this->totalContentsSize(); + IntSize visibleContentSize = visibleContentRect(LegacyIOSDocumentVisibleRect).size(); + if (totalContentsSize.height() <= visibleContentSize.height() && totalContentsSize.width() <= visibleContentSize.width()) + return false; + } // Covers #2. HTMLFrameOwnerElement* owner = frame().ownerElement(); @@ -3111,6 +4002,25 @@ bool FrameView::isScrollable() return true; } +bool FrameView::isScrollableOrRubberbandable() +{ + return isScrollable(Scrollability::ScrollableOrRubberbandable); +} + +bool FrameView::hasScrollableOrRubberbandableAncestor() +{ + if (frame().isMainFrame()) + return isScrollableOrRubberbandable(); + + for (FrameView* parent = this->parentFrameView(); parent; parent = parent->parentFrameView()) { + Scrollability frameScrollability = parent->frame().isMainFrame() ? Scrollability::ScrollableOrRubberbandable : Scrollability::Scrollable; + if (parent->isScrollable(frameScrollability)) + return true; + } + + return false; +} + void FrameView::updateScrollableAreaSet() { // That ensures that only inner frames are cached. @@ -3131,15 +4041,15 @@ bool FrameView::shouldSuspendScrollAnimations() const return frame().loader().state() != FrameStateComplete; } -void FrameView::scrollbarStyleChanged(int newStyle, bool forceUpdate) +void FrameView::scrollbarStyleChanged(ScrollbarStyle newStyle, bool forceUpdate) { if (!frame().isMainFrame()) return; - frame().page()->chrome().client().recommendedScrollbarStyleDidChange(newStyle); + if (Page* page = frame().page()) + page->chrome().client().recommendedScrollbarStyleDidChange(newStyle); - if (forceUpdate) - ScrollView::scrollbarStyleChanged(newStyle, forceUpdate); + ScrollView::scrollbarStyleChanged(newStyle, forceUpdate); } void FrameView::notifyPageThatContentAreaWillPaint() const @@ -3187,14 +4097,14 @@ void FrameView::updateAnnotatedRegions() void FrameView::updateScrollCorner() { - RenderElement* renderer = 0; - RefPtr<RenderStyle> cornerStyle; + RenderElement* renderer = nullptr; + std::unique_ptr<RenderStyle> cornerStyle; IntRect cornerRect = scrollCornerRect(); if (!cornerRect.isEmpty()) { // Try the <body> element first as a scroll corner source. Document* doc = frame().document(); - Element* body = doc ? doc->body() : 0; + Element* body = doc ? doc->bodyOrFrameset() : nullptr; if (body && body->renderer()) { renderer = body->renderer(); cornerStyle = renderer->getUncachedPseudoStyle(PseudoStyleRequest(SCROLLBAR_CORNER), &renderer->style()); @@ -3202,7 +4112,7 @@ void FrameView::updateScrollCorner() if (!cornerStyle) { // If the <body> didn't have a custom style, then the root element might. - Element* docElement = doc ? doc->documentElement() : 0; + Element* docElement = doc ? doc->documentElement() : nullptr; if (docElement && docElement->renderer()) { renderer = docElement->renderer(); cornerStyle = renderer->getUncachedPseudoStyle(PseudoStyleRequest(SCROLLBAR_CORNER), &renderer->style()); @@ -3220,26 +4130,24 @@ void FrameView::updateScrollCorner() m_scrollCorner = nullptr; else { if (!m_scrollCorner) { - m_scrollCorner = createRenderer<RenderScrollbarPart>(renderer->document(), cornerStyle.releaseNonNull()); + m_scrollCorner = createRenderer<RenderScrollbarPart>(renderer->document(), WTFMove(*cornerStyle)); m_scrollCorner->initializeStyle(); } else - m_scrollCorner->setStyle(cornerStyle.releaseNonNull()); + m_scrollCorner->setStyle(WTFMove(*cornerStyle)); invalidateScrollCorner(cornerRect); } - - ScrollView::updateScrollCorner(); } -void FrameView::paintScrollCorner(GraphicsContext* context, const IntRect& cornerRect) +void FrameView::paintScrollCorner(GraphicsContext& context, const IntRect& cornerRect) { - if (context->updatingControlTints()) { + if (context.updatingControlTints()) { updateScrollCorner(); return; } if (m_scrollCorner) { if (frame().isMainFrame()) - context->fillRect(cornerRect, baseBackgroundColor(), ColorSpaceDeviceRGB); + context.fillRect(cornerRect, baseBackgroundColor()); m_scrollCorner->paintIntoRect(context, cornerRect.location(), cornerRect); return; } @@ -3247,12 +4155,12 @@ void FrameView::paintScrollCorner(GraphicsContext* context, const IntRect& corne ScrollView::paintScrollCorner(context, cornerRect); } -void FrameView::paintScrollbar(GraphicsContext* context, Scrollbar* bar, const IntRect& rect) +void FrameView::paintScrollbar(GraphicsContext& context, Scrollbar& bar, const IntRect& rect) { - if (bar->isCustomScrollbar() && frame().isMainFrame()) { - IntRect toFill = bar->frameRect(); + if (bar.isCustomScrollbar() && frame().isMainFrame()) { + IntRect toFill = bar.frameRect(); toFill.intersect(rect); - context->fillRect(toFill, baseBackgroundColor(), ColorSpaceDeviceRGB); + context.fillRect(toFill, baseBackgroundColor()); } ScrollView::paintScrollbar(context, bar, rect); @@ -3268,8 +4176,8 @@ Color FrameView::documentBackgroundColor() const if (!frame().document()) return Color(); - Element* htmlElement = frame().document()->documentElement(); - Element* bodyElement = frame().document()->body(); + auto* htmlElement = frame().document()->documentElement(); + auto* bodyElement = frame().document()->bodyOrFrameset(); // Start with invalid colors. Color htmlBackgroundColor; @@ -3301,46 +4209,48 @@ Color FrameView::documentBackgroundColor() const bool FrameView::hasCustomScrollbars() const { for (auto& widget : children()) { - if (widget->isFrameView()) { - if (toFrameView(*widget).hasCustomScrollbars()) + if (is<FrameView>(widget.get())) { + if (downcast<FrameView>(widget.get()).hasCustomScrollbars()) return true; - } else if (widget->isScrollbar()) { - if (toScrollbar(*widget).isCustomScrollbar()) + } else if (is<Scrollbar>(widget.get())) { + if (downcast<Scrollbar>(widget.get()).isCustomScrollbar()) return true; } } - return false; } FrameView* FrameView::parentFrameView() const { if (!parent()) - return 0; - - if (Frame* parentFrame = frame().tree().parent()) - return parentFrame->view(); - - return 0; + return nullptr; + auto* parentFrame = frame().tree().parent(); + if (!parentFrame) + return nullptr; + return parentFrame->view(); } bool FrameView::isInChildFrameWithFrameFlattening() const { - if (!parent() || !frame().ownerElement()) + if (!frameFlatteningEnabled()) return false; - // Frame flattening applies when the owner element is either in a frameset or - // an iframe with flattening parameters. - if (frame().ownerElement()->hasTagName(iframeTag)) { - RenderIFrame* iframeRenderer = toRenderIFrame(frame().ownerElement()->renderWidget()); - if (iframeRenderer->flattenFrame() || iframeRenderer->isSeamless()) - return true; - } + if (!parent()) + return false; - if (!frameFlatteningEnabled()) + HTMLFrameOwnerElement* ownerElement = frame().ownerElement(); + if (!ownerElement) + return false; + + if (!ownerElement->renderWidget()) return false; - if (frame().ownerElement()->hasTagName(frameTag)) + // Frame flattening applies when the owner element is either in a frameset or + // an iframe with flattening parameters. + if (is<HTMLIFrameElement>(*ownerElement)) + return downcast<RenderIFrame>(*ownerElement->renderWidget()).flattenFrame(); + + if (is<HTMLFrameElement>(*ownerElement)) return true; return false; @@ -3366,10 +4276,8 @@ void FrameView::startLayoutAtMainFrameViewIfNeeded(bool allowSubtree) while (parentView->parentFrameView()) parentView = parentView->parentFrameView(); + LOG(Layout, " frame flattening, starting from root"); parentView->layout(allowSubtree); - - RenderElement* root = m_layoutRoot ? m_layoutRoot : frame().document()->renderView(); - ASSERT_UNUSED(root, !root->needsLayout()); } void FrameView::updateControlTints() @@ -3383,23 +4291,33 @@ void FrameView::updateControlTints() if (frame().document()->url().isEmpty()) return; + // As noted above, this is a "fake" paint, so we should pause counting relevant repainted objects. + Page* page = frame().page(); + bool isCurrentlyCountingRelevantRepaintedObject = false; + if (page) { + isCurrentlyCountingRelevantRepaintedObject = page->isCountingRelevantRepaintedObjects(); + page->setIsCountingRelevantRepaintedObjects(false); + } + RenderView* renderView = this->renderView(); if ((renderView && renderView->theme().supportsControlTints()) || hasCustomScrollbars()) paintControlTints(); + + if (page) + page->setIsCountingRelevantRepaintedObjects(isCurrentlyCountingRelevantRepaintedObject); } void FrameView::paintControlTints() { if (needsLayout()) layout(); - PlatformGraphicsContext* const noContext = 0; - GraphicsContext context(noContext); - context.setUpdatingControlTints(true); + + GraphicsContext context(GraphicsContext::NonPaintingReasons::UpdatingControlTints); if (platformWidget()) { // FIXME: consult paintsEntireContents(). - paintContents(&context, visibleContentRect(LegacyIOSDocumentVisibleRect)); + paintContents(context, visibleContentRect(LegacyIOSDocumentVisibleRect)); } else - paint(&context, frameRect()); + paint(context, frameRect()); } bool FrameView::wasScrolledByUser() const @@ -3411,134 +4329,141 @@ void FrameView::setWasScrolledByUser(bool wasScrolledByUser) { if (m_inProgrammaticScroll) return; - m_maintainScrollPositionAnchor = 0; + m_maintainScrollPositionAnchor = nullptr; if (m_wasScrolledByUser == wasScrolledByUser) return; m_wasScrolledByUser = wasScrolledByUser; + if (frame().isMainFrame()) + updateLayerFlushThrottling(); adjustTiledBackingCoverage(); } -void FrameView::paintContents(GraphicsContext* p, const IntRect& rect) +void FrameView::willPaintContents(GraphicsContext& context, const IntRect&, PaintingState& paintingState) { Document* document = frame().document(); -#ifndef NDEBUG - bool fillWithRed; - if (document->printing()) - fillWithRed = false; // Printing, don't fill with red (can't remember why). - else if (frame().ownerElement()) - fillWithRed = false; // Subframe, don't fill with red. - else if (isTransparent()) - fillWithRed = false; // Transparent, don't fill with red. - else if (m_paintBehavior & PaintBehaviorSelectionOnly) - fillWithRed = false; // Selections are transparent, don't fill with red. - else if (m_nodeToDraw) - fillWithRed = false; // Element images are transparent, don't fill with red. - else - fillWithRed = true; - - if (fillWithRed) - p->fillRect(rect, Color(0xFF, 0, 0), ColorSpaceDeviceRGB); -#endif - - RenderView* renderView = this->renderView(); - if (!renderView) { - LOG_ERROR("called FrameView::paint with nil renderer"); - return; - } - - ASSERT(!needsLayout()); - if (needsLayout()) - return; + if (!context.paintingDisabled()) + InspectorInstrumentation::willPaint(*renderView()); - if (!p->paintingDisabled()) - InspectorInstrumentation::willPaint(renderView); + paintingState.isTopLevelPainter = !sCurrentPaintTimeStamp; - bool isTopLevelPainter = !sCurrentPaintTimeStamp; -#if PLATFORM(IOS) - // FIXME: Remove PLATFORM(IOS)-guard once we upstream the iOS changes to MemoryPressureHandler.h. - if (isTopLevelPainter && memoryPressureHandler().hasReceivedMemoryPressure()) { - LOG(MemoryPressure, "Under memory pressure: %s", __PRETTY_FUNCTION__); + if (paintingState.isTopLevelPainter && MemoryPressureHandler::singleton().isUnderMemoryPressure()) { + LOG(MemoryPressure, "Under memory pressure: %s", WTF_PRETTY_FUNCTION); // To avoid unnecessary image decoding, we don't prune recently-decoded live resources here since // we might need some live bitmaps on painting. - memoryCache()->prune(); + MemoryCache::singleton().prune(); } -#endif - if (isTopLevelPainter) - sCurrentPaintTimeStamp = monotonicallyIncreasingTime(); - - FontCachePurgePreventer fontCachePurgePreventer; -#if USE(ACCELERATED_COMPOSITING) - if (!p->paintingDisabled() && !document->printing()) - flushCompositingStateForThisFrame(&frame()); -#endif + if (paintingState.isTopLevelPainter) + sCurrentPaintTimeStamp = monotonicallyIncreasingTime(); - PaintBehavior oldPaintBehavior = m_paintBehavior; + paintingState.paintBehavior = m_paintBehavior; if (FrameView* parentView = parentFrameView()) { if (parentView->paintBehavior() & PaintBehaviorFlattenCompositingLayers) m_paintBehavior |= PaintBehaviorFlattenCompositingLayers; } - - if (m_paintBehavior == PaintBehaviorNormal) - document->markers().invalidateRenderedRectsForMarkersInRect(rect); if (document->printing()) m_paintBehavior |= PaintBehaviorFlattenCompositingLayers; - bool flatteningPaint = m_paintBehavior & PaintBehaviorFlattenCompositingLayers; - bool isRootFrame = !frame().ownerElement(); - if (flatteningPaint && isRootFrame) + paintingState.isFlatteningPaintOfRootFrame = (m_paintBehavior & PaintBehaviorFlattenCompositingLayers) && !frame().ownerElement(); + if (paintingState.isFlatteningPaintOfRootFrame) notifyWidgetsInAllFrames(WillPaintFlattened); ASSERT(!m_isPainting); m_isPainting = true; +} - // m_nodeToDraw is used to draw only one element (and its descendants) - RenderObject* eltRenderer = m_nodeToDraw ? m_nodeToDraw->renderer() : 0; - RenderLayer* rootLayer = renderView->layer(); - -#ifndef NDEBUG - RenderElement::SetLayoutNeededForbiddenScope forbidSetNeedsLayout(&rootLayer->renderer()); -#endif - - rootLayer->paint(p, rect, m_paintBehavior, eltRenderer); - - if (rootLayer->containsDirtyOverlayScrollbars()) - rootLayer->paintOverlayScrollbars(p, rect, m_paintBehavior, eltRenderer); - +void FrameView::didPaintContents(GraphicsContext& context, const IntRect& dirtyRect, PaintingState& paintingState) +{ m_isPainting = false; - if (flatteningPaint && isRootFrame) + if (paintingState.isFlatteningPaintOfRootFrame) notifyWidgetsInAllFrames(DidPaintFlattened); - m_paintBehavior = oldPaintBehavior; + m_paintBehavior = paintingState.paintBehavior; m_lastPaintTime = monotonicallyIncreasingTime(); -#if PLATFORM(IOS) // Painting can lead to decoding of large amounts of bitmaps // If we are low on memory, wipe them out after the paint. - // FIXME: Remove PLATFORM(IOS)-guard once we upstream the iOS changes to MemoryPressureHandler.h. - if (isTopLevelPainter && memoryPressureHandler().hasReceivedMemoryPressure()) - memoryCache()->pruneLiveResources(true); -#endif + if (paintingState.isTopLevelPainter && MemoryPressureHandler::singleton().isUnderMemoryPressure()) + MemoryCache::singleton().pruneLiveResources(true); // Regions may have changed as a result of the visibility/z-index of element changing. #if ENABLE(DASHBOARD_SUPPORT) - if (document->annotatedRegionsDirty()) + if (frame().document()->annotatedRegionsDirty()) updateAnnotatedRegions(); #endif - if (isTopLevelPainter) + if (paintingState.isTopLevelPainter) sCurrentPaintTimeStamp = 0; - if (!p->paintingDisabled()) { - InspectorInstrumentation::didPaint(renderView, p, rect); + if (!context.paintingDisabled()) { + InspectorInstrumentation::didPaint(*renderView(), dirtyRect); // FIXME: should probably not fire milestones for snapshot painting. https://bugs.webkit.org/show_bug.cgi?id=117623 - firePaintRelatedMilestones(); + firePaintRelatedMilestonesIfNeeded(); + } +} + +void FrameView::paintContents(GraphicsContext& context, const IntRect& dirtyRect, SecurityOriginPaintPolicy securityOriginPaintPolicy) +{ +#ifndef NDEBUG + bool fillWithRed; + if (frame().document()->printing()) + fillWithRed = false; // Printing, don't fill with red (can't remember why). + else if (frame().ownerElement()) + fillWithRed = false; // Subframe, don't fill with red. + else if (isTransparent()) + fillWithRed = false; // Transparent, don't fill with red. + else if (m_paintBehavior & PaintBehaviorSelectionOnly) + fillWithRed = false; // Selections are transparent, don't fill with red. + else if (m_nodeToDraw) + fillWithRed = false; // Element images are transparent, don't fill with red. + else + fillWithRed = true; + + if (fillWithRed) + context.fillRect(dirtyRect, Color(0xFF, 0, 0)); +#endif + + RenderView* renderView = this->renderView(); + if (!renderView) { + LOG_ERROR("called FrameView::paint with nil renderer"); + return; } + + if (!inPaintableState()) + return; + + TraceScope tracingScope(PaintViewStart, PaintViewEnd); + + ASSERT(!needsLayout()); + if (needsLayout()) + return; + + PaintingState paintingState; + willPaintContents(context, dirtyRect, paintingState); + + // m_nodeToDraw is used to draw only one element (and its descendants) + RenderObject* renderer = m_nodeToDraw ? m_nodeToDraw->renderer() : nullptr; + RenderLayer* rootLayer = renderView->layer(); + +#ifndef NDEBUG + RenderElement::SetLayoutNeededForbiddenScope forbidSetNeedsLayout(&rootLayer->renderer()); +#endif + + // To work around http://webkit.org/b/135106, ensure that the paint root isn't an inline with culled line boxes. + // FIXME: This can cause additional content to be included in the snapshot, so remove this once that bug is fixed. + while (is<RenderInline>(renderer) && !downcast<RenderInline>(*renderer).firstLineBox()) + renderer = renderer->parent(); + + rootLayer->paint(context, dirtyRect, LayoutSize(), m_paintBehavior, renderer, 0, securityOriginPaintPolicy == SecurityOriginPaintPolicy::AnyOrigin ? RenderLayer::SecurityOriginPaintPolicy::AnyOrigin : RenderLayer::SecurityOriginPaintPolicy::AccessibleOriginOnly); + if (rootLayer->containsDirtyOverlayScrollbars()) + rootLayer->paintOverlayScrollbars(context, dirtyRect, m_paintBehavior, renderer); + + didPaintContents(context, dirtyRect, paintingState); } void FrameView::setPaintBehavior(PaintBehavior behavior) @@ -3562,7 +4487,7 @@ void FrameView::setNodeToDraw(Node* node) m_nodeToDraw = node; } -void FrameView::paintContentsForSnapshot(GraphicsContext* context, const IntRect& imageRect, SelectionInSnapshot shouldPaintSelection, CoordinateSpaceForSnapshot coordinateSpace) +void FrameView::paintContentsForSnapshot(GraphicsContext& context, const IntRect& imageRect, SelectionInSnapshot shouldPaintSelection, CoordinateSpaceForSnapshot coordinateSpace) { updateLayoutAndStyleIfNeededRecursive(); @@ -3574,7 +4499,7 @@ void FrameView::paintContentsForSnapshot(GraphicsContext* context, const IntRect // in the render tree only. This will allow us to restore the selection from the DOM // after we paint the snapshot. if (shouldPaintSelection == ExcludeSelection) { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) { if (RenderView* root = frame->contentRenderer()) root->clearSelection(); } @@ -3590,7 +4515,7 @@ void FrameView::paintContentsForSnapshot(GraphicsContext* context, const IntRect // Restore selection. if (shouldPaintSelection == ExcludeSelection) { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) frame->selection().updateAppearance(); } @@ -3598,9 +4523,9 @@ void FrameView::paintContentsForSnapshot(GraphicsContext* context, const IntRect setPaintBehavior(oldBehavior); } -void FrameView::paintOverhangAreas(GraphicsContext* context, const IntRect& horizontalOverhangArea, const IntRect& verticalOverhangArea, const IntRect& dirtyRect) +void FrameView::paintOverhangAreas(GraphicsContext& context, const IntRect& horizontalOverhangArea, const IntRect& verticalOverhangArea, const IntRect& dirtyRect) { - if (context->paintingDisabled()) + if (context.paintingDisabled()) return; if (frame().document()->printing()) @@ -3609,6 +4534,17 @@ void FrameView::paintOverhangAreas(GraphicsContext* context, const IntRect& hori ScrollView::paintOverhangAreas(context, horizontalOverhangArea, verticalOverhangArea, dirtyRect); } +FrameView::FrameViewList FrameView::renderedChildFrameViews() const +{ + FrameViewList childViews; + for (Frame* frame = m_frame->tree().firstRenderedChild(); frame; frame = frame->tree().nextRenderedSibling()) { + if (frame->view()) + childViews.append(*frame->view()); + } + + return childViews; +} + void FrameView::updateLayoutAndStyleIfNeededRecursive() { // We have to crawl our entire tree looking for any FrameViews that need @@ -3620,28 +4556,24 @@ void FrameView::updateLayoutAndStyleIfNeededRecursive() // region but then become included later by the second frame adding rects to the dirty region // when it lays out. + AnimationUpdateBlock animationUpdateBlock(&frame().animation()); + frame().document()->updateStyleIfNeeded(); if (needsLayout()) layout(); - // Grab a copy of the children() set, as it may be mutated by the following updateLayoutAndStyleIfNeededRecursive - // calls, as they can potentially re-enter a layout of the parent frame view, which may add/remove scrollbars - // and thus mutates the children() set. - Vector<Ref<FrameView>, 16> childViews; - childViews.reserveInitialCapacity(children().size()); - for (auto& widget : children()) { - if (widget->isFrameView()) - childViews.uncheckedAppend(toFrameView(*widget)); - } + // Grab a copy of the child views, as the list may be mutated by the following updateLayoutAndStyleIfNeededRecursive + // calls, as they can potentially re-enter a layout of the parent frame view. + for (auto& frameView : renderedChildFrameViews()) + frameView->updateLayoutAndStyleIfNeededRecursive(); - for (unsigned i = 0; i < childViews.size(); ++i) - childViews[i]->updateLayoutAndStyleIfNeededRecursive(); + // A child frame may have dirtied us during its layout. + frame().document()->updateStyleIfNeeded(); + if (needsLayout()) + layout(); - // When frame flattening is on, child frame can mark parent frame dirty. In such case, child frame - // needs to call layout on parent frame recursively. - // This assert ensures that parent frames are clean, when child frames finished updating layout and style. - ASSERT(!needsLayout()); + ASSERT(!frame().isMainFrame() || !needsStyleRecalcOrLayout()); } bool FrameView::qualifiesAsVisuallyNonEmpty() const @@ -3652,12 +4584,14 @@ bool FrameView::qualifiesAsVisuallyNonEmpty() const return false; // Ensure that we always get marked visually non-empty eventually. - if (!frame().document()->parsing() && frame().loader().stateMachine()->committedFirstRealDocumentLoad()) + if (!frame().document()->parsing() && frame().loader().stateMachine().committedFirstRealDocumentLoad()) return true; // Require the document to grow a bit. - static const int documentHeightThreshold = 200; - if (documentElement->renderBox()->layoutOverflowRect().pixelSnappedHeight() < documentHeightThreshold) + // Using a value of 48 allows the header on Google's search page to render immediately before search results populate later. + static const int documentHeightThreshold = 48; + LayoutRect overflowRect = documentElement->renderBox()->layoutOverflowRect(); + if (snappedIntRect(overflowRect).height() < documentHeightThreshold) return false; // The first few hundred characters rarely contain the interesting content of the page. @@ -3679,6 +4613,15 @@ void FrameView::updateIsVisuallyNonEmpty() adjustTiledBackingCoverage(); } +bool FrameView::isViewForDocumentInFrame() const +{ + RenderView* renderView = this->renderView(); + if (!renderView) + return false; + + return &renderView->frameView() == this; +} + void FrameView::enableAutoSizeMode(bool enable, const IntSize& minSize, const IntSize& maxSize) { ASSERT(!enable || !minSize.isEmpty()); @@ -3717,10 +4660,8 @@ void FrameView::forceLayoutForPagination(const FloatSize& pageSize, const FloatS float pageLogicalWidth = renderView->style().isHorizontalWritingMode() ? pageSize.width() : pageSize.height(); float pageLogicalHeight = renderView->style().isHorizontalWritingMode() ? pageSize.height() : pageSize.width(); - LayoutUnit flooredPageLogicalWidth = static_cast<LayoutUnit>(pageLogicalWidth); - LayoutUnit flooredPageLogicalHeight = static_cast<LayoutUnit>(pageLogicalHeight); - renderView->setLogicalWidth(flooredPageLogicalWidth); - renderView->setPageLogicalHeight(flooredPageLogicalHeight); + renderView->setLogicalWidth(floor(pageLogicalWidth)); + renderView->setPageLogicalHeight(floor(pageLogicalHeight)); renderView->setNeedsLayoutAndPrefWidthsRecalc(); forceLayout(); @@ -3738,10 +4679,8 @@ void FrameView::forceLayoutForPagination(const FloatSize& pageSize, const FloatS pageLogicalWidth = horizontalWritingMode ? maxPageSize.width() : maxPageSize.height(); pageLogicalHeight = horizontalWritingMode ? maxPageSize.height() : maxPageSize.width(); - flooredPageLogicalWidth = static_cast<LayoutUnit>(pageLogicalWidth); - flooredPageLogicalHeight = static_cast<LayoutUnit>(pageLogicalHeight); - renderView->setLogicalWidth(flooredPageLogicalWidth); - renderView->setPageLogicalHeight(flooredPageLogicalHeight); + renderView->setLogicalWidth(floor(pageLogicalWidth)); + renderView->setPageLogicalHeight(floor(pageLogicalHeight)); renderView->setNeedsLayoutAndPrefWidthsRecalc(); forceLayout(); @@ -3774,35 +4713,34 @@ void FrameView::adjustPageHeightDeprecated(float *newBottom, float oldTop, float } // Use a context with painting disabled. - GraphicsContext context((PlatformGraphicsContext*)0); + GraphicsContext context((PlatformGraphicsContext*)nullptr); renderView->setTruncatedAt(static_cast<int>(floorf(oldBottom))); IntRect dirtyRect(0, static_cast<int>(floorf(oldTop)), renderView->layoutOverflowRect().maxX(), static_cast<int>(ceilf(oldBottom - oldTop))); renderView->setPrintRect(dirtyRect); - renderView->layer()->paint(&context, dirtyRect); + renderView->layer()->paint(context, dirtyRect); *newBottom = renderView->bestTruncatedAt(); if (!*newBottom) *newBottom = oldBottom; renderView->setPrintRect(IntRect()); } -IntRect FrameView::convertFromRenderer(const RenderElement* renderer, const IntRect& rendererRect) const +IntRect FrameView::convertFromRendererToContainingView(const RenderElement* renderer, const IntRect& rendererRect) const { - IntRect rect = pixelSnappedIntRect(enclosingLayoutRect(renderer->localToAbsoluteQuad(FloatRect(rendererRect)).boundingBox())); + IntRect rect = snappedIntRect(enclosingLayoutRect(renderer->localToAbsoluteQuad(FloatRect(rendererRect)).boundingBox())); - // Convert from page ("absolute") to FrameView coordinates. if (!delegatesScrolling()) - rect.moveBy(-scrollPosition() + IntPoint(0, headerHeight())); + rect = contentsToView(rect); return rect; } -IntRect FrameView::convertToRenderer(const RenderElement* renderer, const IntRect& viewRect) const +IntRect FrameView::convertFromContainingViewToRenderer(const RenderElement* renderer, const IntRect& viewRect) const { IntRect rect = viewRect; // Convert from FrameView coords into page ("absolute") coordinates. if (!delegatesScrolling()) - rect.moveBy(scrollPositionRelativeToDocument()); + rect = viewToContents(rect); // FIXME: we don't have a way to map an absolute rect down to a local quad, so just // move the rect for now. @@ -3810,23 +4748,24 @@ IntRect FrameView::convertToRenderer(const RenderElement* renderer, const IntRec return rect; } -IntPoint FrameView::convertFromRenderer(const RenderElement* renderer, const IntPoint& rendererPoint) const +IntPoint FrameView::convertFromRendererToContainingView(const RenderElement* renderer, const IntPoint& rendererPoint) const { IntPoint point = roundedIntPoint(renderer->localToAbsolute(rendererPoint, UseTransforms)); // Convert from page ("absolute") to FrameView coordinates. if (!delegatesScrolling()) - point.moveBy(-scrollPosition() + IntPoint(0, headerHeight())); + point = contentsToView(point); + return point; } -IntPoint FrameView::convertToRenderer(const RenderElement* renderer, const IntPoint& viewPoint) const +IntPoint FrameView::convertFromContainingViewToRenderer(const RenderElement* renderer, const IntPoint& viewPoint) const { IntPoint point = viewPoint; // Convert from FrameView coords into page ("absolute") coordinates. if (!delegatesScrolling()) - point = point + scrollPositionRelativeToDocument(); + point = viewToContents(point); return roundedIntPoint(renderer->absoluteToLocal(point, UseTransforms)); } @@ -3834,8 +4773,8 @@ IntPoint FrameView::convertToRenderer(const RenderElement* renderer, const IntPo IntRect FrameView::convertToContainingView(const IntRect& localRect) const { if (const ScrollView* parentScrollView = parent()) { - if (parentScrollView->isFrameView()) { - const FrameView* parentView = toFrameView(parentScrollView); + if (is<FrameView>(*parentScrollView)) { + const FrameView& parentView = downcast<FrameView>(*parentScrollView); // Get our renderer in the parent view RenderWidget* renderer = frame().ownerRenderer(); if (!renderer) @@ -3845,7 +4784,7 @@ IntRect FrameView::convertToContainingView(const IntRect& localRect) const // Add borders and padding?? rect.move(renderer->borderLeft() + renderer->paddingLeft(), renderer->borderTop() + renderer->paddingTop()); - return parentView->convertFromRenderer(renderer, rect); + return parentView.convertFromRendererToContainingView(renderer, rect); } return Widget::convertToContainingView(localRect); @@ -3857,15 +4796,15 @@ IntRect FrameView::convertToContainingView(const IntRect& localRect) const IntRect FrameView::convertFromContainingView(const IntRect& parentRect) const { if (const ScrollView* parentScrollView = parent()) { - if (parentScrollView->isFrameView()) { - const FrameView* parentView = toFrameView(parentScrollView); + if (is<FrameView>(*parentScrollView)) { + const FrameView& parentView = downcast<FrameView>(*parentScrollView); // Get our renderer in the parent view RenderWidget* renderer = frame().ownerRenderer(); if (!renderer) return parentRect; - IntRect rect = parentView->convertToRenderer(renderer, parentRect); + IntRect rect = parentView.convertFromContainingViewToRenderer(renderer, parentRect); // Subtract borders and padding rect.move(-renderer->borderLeft() - renderer->paddingLeft(), -renderer->borderTop() - renderer->paddingTop()); @@ -3881,8 +4820,8 @@ IntRect FrameView::convertFromContainingView(const IntRect& parentRect) const IntPoint FrameView::convertToContainingView(const IntPoint& localPoint) const { if (const ScrollView* parentScrollView = parent()) { - if (parentScrollView->isFrameView()) { - const FrameView* parentView = toFrameView(parentScrollView); + if (is<FrameView>(*parentScrollView)) { + const FrameView& parentView = downcast<FrameView>(*parentScrollView); // Get our renderer in the parent view RenderWidget* renderer = frame().ownerRenderer(); @@ -3894,7 +4833,7 @@ IntPoint FrameView::convertToContainingView(const IntPoint& localPoint) const // Add borders and padding point.move(renderer->borderLeft() + renderer->paddingLeft(), renderer->borderTop() + renderer->paddingTop()); - return parentView->convertFromRenderer(renderer, point); + return parentView.convertFromRendererToContainingView(renderer, point); } return Widget::convertToContainingView(localPoint); @@ -3906,15 +4845,15 @@ IntPoint FrameView::convertToContainingView(const IntPoint& localPoint) const IntPoint FrameView::convertFromContainingView(const IntPoint& parentPoint) const { if (const ScrollView* parentScrollView = parent()) { - if (parentScrollView->isFrameView()) { - const FrameView* parentView = toFrameView(parentScrollView); + if (is<FrameView>(*parentScrollView)) { + const FrameView& parentView = downcast<FrameView>(*parentScrollView); // Get our renderer in the parent view RenderWidget* renderer = frame().ownerRenderer(); if (!renderer) return parentPoint; - IntPoint point = parentView->convertToRenderer(renderer, parentPoint); + IntPoint point = parentView.convertFromContainingViewToRenderer(renderer, parentPoint); // Subtract borders and padding point.move(-renderer->borderLeft() - renderer->paddingLeft(), -renderer->borderTop() - renderer->paddingTop()); @@ -3938,12 +4877,10 @@ void FrameView::setTracksRepaints(bool trackRepaints) frame().document()->updateLayout(); } -#if USE(ACCELERATED_COMPOSITING) for (Frame* frame = &m_frame->tree().top(); frame; frame = frame->tree().traverseNext()) { if (RenderView* renderView = frame->contentRenderer()) renderView->compositor().setTracksRepaints(trackRepaints); } -#endif resetTrackedRepaints(); m_isTrackingRepaints = trackRepaints; @@ -3952,10 +4889,8 @@ void FrameView::setTracksRepaints(bool trackRepaints) void FrameView::resetTrackedRepaints() { m_trackedRepaintRects.clear(); -#if USE(ACCELERATED_COMPOSITING) if (RenderView* renderView = this->renderView()) renderView->compositor().resetTrackedRepaintRects(); -#endif } String FrameView::trackedRepaintRectsAsText() const @@ -3966,8 +4901,8 @@ String FrameView::trackedRepaintRectsAsText() const TextStream ts; if (!m_trackedRepaintRects.isEmpty()) { ts << "(repaint rects\n"; - for (size_t i = 0; i < m_trackedRepaintRects.size(); ++i) - ts << " (rect " << m_trackedRepaintRects[i].x() << " " << m_trackedRepaintRects[i].y() << " " << m_trackedRepaintRects[i].width() << " " << m_trackedRepaintRects[i].height() << ")\n"; + for (auto& rect : m_trackedRepaintRects) + ts << " (rect " << LayoutUnit(rect.x()) << " " << LayoutUnit(rect.y()) << " " << LayoutUnit(rect.width()) << " " << LayoutUnit(rect.height()) << ")\n"; ts << ")\n"; } return ts.release(); @@ -3976,13 +4911,23 @@ String FrameView::trackedRepaintRectsAsText() const bool FrameView::addScrollableArea(ScrollableArea* scrollableArea) { if (!m_scrollableAreas) - m_scrollableAreas = adoptPtr(new ScrollableAreaSet); - return m_scrollableAreas->add(scrollableArea).isNewEntry; + m_scrollableAreas = std::make_unique<ScrollableAreaSet>(); + + if (m_scrollableAreas->add(scrollableArea).isNewEntry) { + scrollableAreaSetChanged(); + return true; + } + + return false; } bool FrameView::removeScrollableArea(ScrollableArea* scrollableArea) { - return m_scrollableAreas && m_scrollableAreas->remove(scrollableArea); + if (m_scrollableAreas && m_scrollableAreas->remove(scrollableArea)) { + scrollableAreaSetChanged(); + return true; + } + return false; } bool FrameView::containsScrollableArea(ScrollableArea* scrollableArea) const @@ -3990,10 +4935,27 @@ bool FrameView::containsScrollableArea(ScrollableArea* scrollableArea) const return m_scrollableAreas && m_scrollableAreas->contains(scrollableArea); } -void FrameView::removeChild(Widget* widget) +void FrameView::scrollableAreaSetChanged() +{ + if (auto* page = frame().page()) { + if (auto* scrollingCoordinator = page->scrollingCoordinator()) + scrollingCoordinator->frameViewEventTrackingRegionsChanged(*this); + } +} + +void FrameView::sendScrollEvent() +{ + frame().eventHandler().sendScrollEvent(); + frame().eventHandler().dispatchFakeMouseMoveEventSoon(); +#if ENABLE(CSS_ANIMATIONS_LEVEL_2) + frame().animation().scrollWasUpdated(); +#endif +} + +void FrameView::removeChild(Widget& widget) { - if (widget->isFrameView()) - removeScrollableArea(toFrameView(widget)); + if (is<FrameView>(widget)) + removeScrollableArea(&downcast<FrameView>(widget)); ScrollView::removeChild(widget); } @@ -4008,12 +4970,12 @@ bool FrameView::wheelEvent(const PlatformWheelEvent& wheelEvent) #endif if (delegatesScrolling()) { - IntSize offset = scrollOffset(); - IntSize newOffset = IntSize(offset.width() - wheelEvent.deltaX(), offset.height() - wheelEvent.deltaY()); - if (offset != newOffset) { - ScrollView::scrollTo(newOffset); - scrollPositionChanged(); - frame().loader().client().didChangeScrollOffset(); + ScrollPosition oldPosition = scrollPosition(); + ScrollPosition newPosition = oldPosition - IntSize(wheelEvent.deltaX(), wheelEvent.deltaY()); + if (oldPosition != newPosition) { + ScrollView::scrollTo(newPosition); + scrollPositionChanged(oldPosition, scrollPosition()); + didChangeScrollOffset(); } return true; } @@ -4028,8 +4990,8 @@ bool FrameView::wheelEvent(const PlatformWheelEvent& wheelEvent) #if ENABLE(ASYNC_SCROLLING) if (Page* page = frame().page()) { if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) { - if (scrollingCoordinator->coordinatesScrollingForFrameView(this)) - return scrollingCoordinator->handleWheelEvent(this, wheelEvent); + if (scrollingCoordinator->coordinatesScrollingForFrameView(*this)) + return scrollingCoordinator->handleWheelEvent(*this, wheelEvent); } } #endif @@ -4058,7 +5020,7 @@ bool FrameView::isFlippedDocument() const void FrameView::notifyWidgetsInAllFrames(WidgetNotification notification) { - for (Frame* frame = m_frame.get(); frame; frame = frame->tree().traverseNext(m_frame.get())) { + for (auto* frame = m_frame.ptr(); frame; frame = frame->tree().traverseNext(m_frame.ptr())) { if (FrameView* view = frame->view()) view->notifyWidgets(notification); } @@ -4068,16 +5030,13 @@ AXObjectCache* FrameView::axObjectCache() const { if (frame().document()) return frame().document()->existingAXObjectCache(); - return 0; + return nullptr; } - + #if PLATFORM(IOS) -void FrameView::setUseCustomFixedPositionLayoutRect(bool useCustomFixedPositionLayoutRect) +bool FrameView::useCustomFixedPositionLayoutRect() const { - if (m_useCustomFixedPositionLayoutRect == useCustomFixedPositionLayoutRect) - return; - m_useCustomFixedPositionLayoutRect = useCustomFixedPositionLayoutRect; - visibleContentsResized(); + return !frame().settings().visualViewportEnabled() && m_useCustomFixedPositionLayoutRect; } void FrameView::setCustomFixedPositionLayoutRect(const IntRect& rect) @@ -4086,7 +5045,7 @@ void FrameView::setCustomFixedPositionLayoutRect(const IntRect& rect) return; m_useCustomFixedPositionLayoutRect = true; m_customFixedPositionLayoutRect = rect; - visibleContentsResized(); + updateContentsSize(); } bool FrameView::updateFixedPositionLayoutRect() @@ -4106,21 +5065,33 @@ bool FrameView::updateFixedPositionLayoutRect() } return false; } + +void FrameView::setCustomSizeForResizeEvent(IntSize customSize) +{ + m_useCustomSizeForResizeEvent = true; + m_customSizeForResizeEvent = customSize; + sendResizeEventIfNeeded(); +} + +void FrameView::setScrollVelocity(double horizontalVelocity, double verticalVelocity, double scaleChangeRate, MonotonicTime timestamp) +{ + if (TiledBacking* tiledBacking = this->tiledBacking()) + tiledBacking->setVelocity(VelocityData(horizontalVelocity, verticalVelocity, scaleChangeRate, timestamp)); +} #endif // PLATFORM(IOS) void FrameView::setScrollingPerformanceLoggingEnabled(bool flag) { -#if USE(ACCELERATED_COMPOSITING) if (TiledBacking* tiledBacking = this->tiledBacking()) tiledBacking->setScrollingPerformanceLoggingEnabled(flag); -#else - UNUSED_PARAM(flag); -#endif } void FrameView::didAddScrollbar(Scrollbar* scrollbar, ScrollbarOrientation orientation) { ScrollableArea::didAddScrollbar(scrollbar, orientation); + Page* page = frame().page(); + if (page && page->expectsWheelEventTriggers()) + scrollAnimator().setWheelEventTestTrigger(page->testTrigger()); if (AXObjectCache* cache = axObjectCache()) cache->handleScrollbarUpdate(this); } @@ -4139,7 +5110,36 @@ void FrameView::addPaintPendingMilestones(LayoutMilestones milestones) m_milestonesPendingPaint |= milestones; } -void FrameView::firePaintRelatedMilestones() +void FrameView::fireLayoutRelatedMilestonesIfNeeded() +{ + LayoutMilestones requestedMilestones = 0; + LayoutMilestones milestonesAchieved = 0; + Page* page = frame().page(); + if (page) + requestedMilestones = page->requestedLayoutMilestones(); + + if (m_firstLayoutCallbackPending) { + m_firstLayoutCallbackPending = false; + frame().loader().didFirstLayout(); + if (requestedMilestones & DidFirstLayout) + milestonesAchieved |= DidFirstLayout; + if (frame().isMainFrame()) + page->startCountingRelevantRepaintedObjects(); + } + updateIsVisuallyNonEmpty(); + + // If the layout was done with pending sheets, we are not in fact visually non-empty yet. + if (m_isVisuallyNonEmpty && !frame().document()->didLayoutWithPendingStylesheets() && m_firstVisuallyNonEmptyLayoutCallbackPending) { + m_firstVisuallyNonEmptyLayoutCallbackPending = false; + if (requestedMilestones & DidFirstVisuallyNonEmptyLayout) + milestonesAchieved |= DidFirstVisuallyNonEmptyLayout; + } + + if (milestonesAchieved && frame().isMainFrame()) + frame().loader().didReachLayoutMilestone(milestonesAchieved); +} + +void FrameView::firePaintRelatedMilestonesIfNeeded() { Page* page = frame().page(); if (!page) @@ -4161,7 +5161,7 @@ void FrameView::firePaintRelatedMilestones() m_milestonesPendingPaint = 0; if (milestonesAchieved) - page->mainFrame().loader().didLayout(milestonesAchieved); + page->mainFrame().loader().didReachLayoutMilestone(milestonesAchieved); } void FrameView::setVisualUpdatesAllowedByClient(bool visualUpdatesAllowed) @@ -4178,10 +5178,12 @@ void FrameView::setScrollPinningBehavior(ScrollPinningBehavior pinning) { m_scrollPinningBehavior = pinning; - if (ScrollingCoordinator* scrollingCoordinator = frame().page()->scrollingCoordinator()) - scrollingCoordinator->setScrollPinningBehavior(pinning); + if (Page* page = frame().page()) { + if (auto* scrollingCoordinator = page->scrollingCoordinator()) + scrollingCoordinator->setScrollPinningBehavior(pinning); + } - updateScrollbars(scrollOffset()); + updateScrollbars(scrollPosition()); } ScrollBehaviorForFixedElements FrameView::scrollBehaviorForFixedElements() const @@ -4228,36 +5230,75 @@ void FrameView::updateWidgetPositions() // updateWidgetPosition() can possibly cause layout to be re-entered (via plug-ins running // scripts in response to NPP_SetWindow, for example), so we need to keep the Widgets // alive during enumeration. - auto protectedWidgets = collectAndProtectWidgets(m_widgetsInRenderTree); - - for (unsigned i = 0, size = protectedWidgets.size(); i < size; ++i) { - if (RenderWidget* renderWidget = RenderWidget::find(protectedWidgets[i].get())) - renderWidget->updateWidgetPosition(); + for (auto& widget : collectAndProtectWidgets(m_widgetsInRenderTree)) { + if (auto* renderer = RenderWidget::find(*widget)) { + auto ignoreWidgetState = renderer->updateWidgetPosition(); + UNUSED_PARAM(ignoreWidgetState); + } } } void FrameView::notifyWidgets(WidgetNotification notification) { - auto protectedWidgets = collectAndProtectWidgets(m_widgetsInRenderTree); - - for (unsigned i = 0, size = protectedWidgets.size(); i < size; ++i) - protectedWidgets[i]->notifyWidget(notification); + for (auto& widget : collectAndProtectWidgets(m_widgetsInRenderTree)) + widget->notifyWidget(notification); } -void FrameView::setExposedRect(FloatRect exposedRect) +void FrameView::setViewExposedRect(std::optional<FloatRect> viewExposedRect) { - if (m_exposedRect == exposedRect) + if (m_viewExposedRect == viewExposedRect) return; - m_exposedRect = exposedRect; + LOG_WITH_STREAM(Scrolling, stream << "FrameView " << this << " setViewExposedRect " << (viewExposedRect ? viewExposedRect.value() : FloatRect())); + + bool hasRectChanged = !m_viewExposedRect == !viewExposedRect; + m_viewExposedRect = viewExposedRect; -#if USE(ACCELERATED_COMPOSITING) // FIXME: We should support clipping to the exposed rect for subframes as well. - if (m_frame->isMainFrame()) { - if (TiledBacking* tiledBacking = this->tiledBacking()) - tiledBacking->setExposedRect(exposedRect); + if (!frame().isMainFrame()) + return; + + if (TiledBacking* tiledBacking = this->tiledBacking()) { + if (hasRectChanged) + updateTiledBackingAdaptiveSizing(); + adjustTiledBackingCoverage(); + tiledBacking->setTiledScrollingIndicatorPosition(m_viewExposedRect ? m_viewExposedRect.value().location() : FloatPoint()); } -#endif + + if (auto* view = renderView()) + view->compositor().scheduleLayerFlush(false /* canThrottle */); + + frame().mainFrame().pageOverlayController().didChangeViewExposedRect(); +} + +void FrameView::setViewportSizeForCSSViewportUnits(IntSize size) +{ + if (m_hasOverrideViewportSize && m_overrideViewportSize == size) + return; + + m_overrideViewportSize = size; + m_hasOverrideViewportSize = true; + + if (Document* document = frame().document()) + document->styleScope().didChangeStyleSheetEnvironment(); +} + +IntSize FrameView::viewportSizeForCSSViewportUnits() const +{ + if (m_hasOverrideViewportSize) + return m_overrideViewportSize; + + if (useFixedLayout()) + return fixedLayoutSize(); + + // FIXME: the value returned should take into account the value of the overflow + // property on the root element. + return visibleContentRectIncludingScrollbars().size(); } +bool FrameView::shouldPlaceBlockDirectionScrollbarOnLeft() const +{ + return renderView() && renderView()->shouldPlaceBlockDirectionScrollbarOnLeft(); +} + } // namespace WebCore |