| /* |
| * Copyright (C) 2017 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "FrameViewLayoutContext.h" |
| |
| #include "CSSAnimationController.h" |
| #include "DebugPageOverlays.h" |
| #include "Document.h" |
| #include "FrameView.h" |
| #include "InspectorInstrumentation.h" |
| #include "LayoutDisallowedScope.h" |
| #include "Logging.h" |
| #include "RenderElement.h" |
| #include "RenderLayoutState.h" |
| #include "RenderView.h" |
| #include "RuntimeEnabledFeatures.h" |
| #include "ScriptDisallowedScope.h" |
| #include "Settings.h" |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| #include "InvalidationContext.h" |
| #include "InvalidationState.h" |
| #include "LayoutContext.h" |
| #include "LayoutState.h" |
| #include "LayoutTreeBuilder.h" |
| #endif |
| |
| #include <wtf/SetForScope.h> |
| #include <wtf/SystemTracing.h> |
| #include <wtf/text/TextStream.h> |
| |
| namespace WebCore { |
| |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| void FrameViewLayoutContext::layoutUsingFormattingContext() |
| { |
| if (!RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextEnabled()) |
| return; |
| |
| if (!m_layoutTreeContent) { |
| m_layoutTreeContent = Layout::TreeBuilder::buildLayoutTree(*renderView()); |
| // FIXME: New layout tree requires a new state for now. |
| m_layoutState = nullptr; |
| } |
| if (!m_layoutState) |
| m_layoutState = makeUnique<Layout::LayoutState>(*m_layoutTreeContent); |
| |
| // FIXME: This is not the real invalidation yet. |
| auto invalidationState = Layout::InvalidationState { }; |
| auto invalidationContext = Layout::InvalidationContext { invalidationState }; |
| invalidationContext.styleChanged(*m_layoutState->root().firstChild(), StyleDifference::Layout); |
| |
| auto layoutContext = Layout::LayoutContext { *m_layoutState }; |
| layoutContext.layout(renderView()->size(), invalidationState); |
| #ifndef NDEBUG |
| Layout::LayoutContext::verifyAndOutputMismatchingLayoutTree(*m_layoutState); |
| #endif |
| } |
| |
| void FrameViewLayoutContext::invalidateLayoutTreeContent() |
| { |
| m_layoutTreeContent = nullptr; |
| } |
| #endif |
| |
| static bool isObjectAncestorContainerOf(RenderElement& ancestor, RenderElement& descendant) |
| { |
| for (auto* renderer = &descendant; renderer; renderer = renderer->container()) { |
| if (renderer == &ancestor) |
| return true; |
| } |
| return 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 |
| |
| class LayoutScope { |
| public: |
| LayoutScope(FrameViewLayoutContext& layoutContext) |
| : m_view(layoutContext.view()) |
| , m_nestedState(layoutContext.m_layoutNestedState, layoutContext.m_layoutNestedState == FrameViewLayoutContext::LayoutNestedState::NotInLayout ? FrameViewLayoutContext::LayoutNestedState::NotNested : FrameViewLayoutContext::LayoutNestedState::Nested) |
| , m_schedulingIsEnabled(layoutContext.m_layoutSchedulingIsEnabled, false) |
| , m_previousScrollType(layoutContext.view().currentScrollType()) |
| { |
| m_view.setCurrentScrollType(ScrollType::Programmatic); |
| } |
| |
| ~LayoutScope() |
| { |
| m_view.setCurrentScrollType(m_previousScrollType); |
| } |
| |
| private: |
| FrameView& m_view; |
| SetForScope<FrameViewLayoutContext::LayoutNestedState> m_nestedState; |
| SetForScope<bool> m_schedulingIsEnabled; |
| ScrollType m_previousScrollType; |
| }; |
| |
| FrameViewLayoutContext::FrameViewLayoutContext(FrameView& frameView) |
| : m_frameView(frameView) |
| , m_layoutTimer(*this, &FrameViewLayoutContext::layoutTimerFired) |
| , m_asynchronousTasksTimer(*this, &FrameViewLayoutContext::runAsynchronousTasks) |
| { |
| } |
| |
| FrameViewLayoutContext::~FrameViewLayoutContext() |
| { |
| } |
| |
| void FrameViewLayoutContext::layout() |
| { |
| LOG_WITH_STREAM(Layout, stream << "FrameView " << &view() << " FrameViewLayoutContext::layout() with size " << view().layoutSize()); |
| |
| RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(!frame().document()->inRenderTreeUpdate()); |
| ASSERT(LayoutDisallowedScope::isLayoutAllowed()); |
| ASSERT(!view().isPainting()); |
| ASSERT(frame().view() == &view()); |
| ASSERT(frame().document()); |
| ASSERT(frame().document()->backForwardCacheState() == Document::NotInBackForwardCache |
| || frame().document()->backForwardCacheState() == Document::AboutToEnterBackForwardCache); |
| if (!canPerformLayout()) { |
| LOG(Layout, " is not allowed, bailing"); |
| return; |
| } |
| |
| Ref<FrameView> protectView(view()); |
| LayoutScope layoutScope(*this); |
| TraceScope tracingScope(LayoutStart, LayoutEnd); |
| InspectorInstrumentation::willLayout(view().frame()); |
| AnimationUpdateBlock animationUpdateBlock(&view().frame().animation()); |
| WeakPtr<RenderElement> layoutRoot; |
| |
| m_layoutTimer.stop(); |
| m_delayedLayout = false; |
| m_setNeedsLayoutWasDeferred = false; |
| |
| #if !LOG_DISABLED |
| if (m_firstLayout && !frame().ownerElement()) |
| LOG(Layout, "FrameView %p elapsed time before first layout: %.3fs", this, document()->timeSinceDocumentCreation().value()); |
| #endif |
| #if PLATFORM(IOS_FAMILY) |
| if (view().updateFixedPositionLayoutRect() && subtreeLayoutRoot()) |
| convertSubtreeLayoutToFullLayout(); |
| #endif |
| if (handleLayoutWithFrameFlatteningIfNeeded()) |
| return; |
| |
| { |
| SetForScope<LayoutPhase> layoutPhase(m_layoutPhase, LayoutPhase::InPreLayout); |
| |
| // If this is a new top-level layout and there are any remaining tasks from the previous layout, finish them now. |
| if (!isLayoutNested() && m_asynchronousTasksTimer.isActive() && !view().isInChildFrameWithFrameFlattening()) |
| runAsynchronousTasks(); |
| |
| updateStyleForLayout(); |
| if (view().hasOneRef()) |
| return; |
| |
| view().autoSizeIfEnabled(); |
| if (!renderView()) |
| return; |
| |
| layoutRoot = makeWeakPtr(subtreeLayoutRoot() ? subtreeLayoutRoot() : renderView()); |
| m_needsFullRepaint = is<RenderView>(layoutRoot.get()) && (m_firstLayout || renderView()->printing()); |
| view().willDoLayout(layoutRoot); |
| m_firstLayout = false; |
| } |
| { |
| SetForScope<LayoutPhase> layoutPhase(m_layoutPhase, LayoutPhase::InRenderTreeLayout); |
| ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
| SubtreeLayoutStateMaintainer subtreeLayoutStateMaintainer(subtreeLayoutRoot()); |
| RenderView::RepaintRegionAccumulator repaintRegionAccumulator(renderView()); |
| #ifndef NDEBUG |
| RenderTreeNeedsLayoutChecker checker(*layoutRoot); |
| #endif |
| layoutRoot->layout(); |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| layoutUsingFormattingContext(); |
| #endif |
| ++m_layoutCount; |
| #if ENABLE(TEXT_AUTOSIZING) |
| applyTextSizingIfNeeded(*layoutRoot.get()); |
| #endif |
| clearSubtreeLayoutRoot(); |
| } |
| { |
| SetForScope<LayoutPhase> layoutPhase(m_layoutPhase, LayoutPhase::InViewSizeAdjust); |
| if (is<RenderView>(layoutRoot.get()) && !renderView()->printing()) { |
| // This is to protect m_needsFullRepaint's value when layout() is getting re-entered through adjustViewSize(). |
| SetForScope<bool> needsFullRepaint(m_needsFullRepaint); |
| view().adjustViewSize(); |
| // FIXME: Firing media query callbacks synchronously on nested frames could produced a detached FrameView here by |
| // navigating away from the current document (see webkit.org/b/173329). |
| if (view().hasOneRef()) |
| return; |
| } |
| } |
| { |
| SetForScope<LayoutPhase> layoutPhase(m_layoutPhase, LayoutPhase::InPostLayout); |
| if (m_needsFullRepaint) |
| renderView()->repaintRootContents(); |
| ASSERT(!layoutRoot->needsLayout()); |
| view().didLayout(layoutRoot); |
| runOrScheduleAsynchronousTasks(); |
| } |
| InspectorInstrumentation::didLayout(view().frame(), *layoutRoot); |
| DebugPageOverlays::didLayout(view().frame()); |
| } |
| |
| void FrameViewLayoutContext::runOrScheduleAsynchronousTasks() |
| { |
| if (m_asynchronousTasksTimer.isActive()) |
| return; |
| |
| if (view().isInChildFrameWithFrameFlattening()) { |
| // While flattening frames, we defer post layout tasks to avoid getting stuck in a cycle, |
| // except updateWidgetPositions() which is required to kick off subframe layout in certain cases. |
| if (!m_inAsynchronousTasks) |
| view().updateWidgetPositions(); |
| m_asynchronousTasksTimer.startOneShot(0_s); |
| return; |
| } |
| |
| // If we are already in performPostLayoutTasks(), defer post layout tasks until after we return |
| // to avoid re-entrancy. |
| if (m_inAsynchronousTasks) { |
| m_asynchronousTasksTimer.startOneShot(0_s); |
| return; |
| } |
| |
| runAsynchronousTasks(); |
| if (needsLayout()) { |
| // If runAsynchronousTasks() made us layout again, let's defer the tasks until after we return. |
| m_asynchronousTasksTimer.startOneShot(0_s); |
| layout(); |
| } |
| } |
| |
| void FrameViewLayoutContext::runAsynchronousTasks() |
| { |
| m_asynchronousTasksTimer.stop(); |
| if (m_inAsynchronousTasks) |
| return; |
| SetForScope<bool> inAsynchronousTasks(m_inAsynchronousTasks, true); |
| view().performPostLayoutTasks(); |
| } |
| |
| void FrameViewLayoutContext::flushAsynchronousTasks() |
| { |
| if (!m_asynchronousTasksTimer.isActive()) |
| return; |
| runAsynchronousTasks(); |
| } |
| |
| void FrameViewLayoutContext::reset() |
| { |
| m_layoutPhase = LayoutPhase::OutsideLayout; |
| clearSubtreeLayoutRoot(); |
| m_layoutCount = 0; |
| m_layoutSchedulingIsEnabled = true; |
| m_delayedLayout = false; |
| m_layoutTimer.stop(); |
| m_firstLayout = true; |
| m_asynchronousTasksTimer.stop(); |
| m_needsFullRepaint = true; |
| } |
| |
| bool FrameViewLayoutContext::needsLayout() const |
| { |
| // This can return true in cases where the document does not have a body yet. |
| // Document::shouldScheduleLayout takes care of preventing us from scheduling |
| // layout in that case. |
| auto* renderView = this->renderView(); |
| return isLayoutPending() |
| || (renderView && renderView->needsLayout()) |
| || subtreeLayoutRoot() |
| || (m_disableSetNeedsLayoutCount && m_setNeedsLayoutWasDeferred); |
| } |
| |
| void FrameViewLayoutContext::setNeedsLayoutAfterViewConfigurationChange() |
| { |
| if (m_disableSetNeedsLayoutCount) { |
| m_setNeedsLayoutWasDeferred = true; |
| return; |
| } |
| |
| if (auto* renderView = this->renderView()) { |
| ASSERT(!frame().document()->inHitTesting()); |
| renderView->setNeedsLayout(); |
| scheduleLayout(); |
| } |
| } |
| |
| void FrameViewLayoutContext::enableSetNeedsLayout() |
| { |
| ASSERT(m_disableSetNeedsLayoutCount); |
| if (!--m_disableSetNeedsLayoutCount) |
| m_setNeedsLayoutWasDeferred = false; // FIXME: Find a way to make the deferred layout actually happen. |
| } |
| |
| void FrameViewLayoutContext::disableSetNeedsLayout() |
| { |
| ++m_disableSetNeedsLayoutCount; |
| } |
| |
| void FrameViewLayoutContext::scheduleLayout() |
| { |
| // FIXME: We should assert the page is not in the back/forward cache, but that is causing |
| // too many false assertions. See <rdar://problem/7218118>. |
| ASSERT(frame().view() == &view()); |
| |
| if (subtreeLayoutRoot()) |
| convertSubtreeLayoutToFullLayout(); |
| if (!isLayoutSchedulingEnabled()) |
| return; |
| if (!needsLayout()) |
| return; |
| if (!frame().document()->shouldScheduleLayout()) |
| return; |
| 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() && view().isInChildFrameWithFrameFlattening()) |
| frame().ownerRenderer()->setNeedsLayout(MarkContainingBlockChain); |
| |
| Seconds delay = frame().document()->minimumLayoutDelay(); |
| if (m_layoutTimer.isActive() && m_delayedLayout && !delay) |
| unscheduleLayout(); |
| |
| if (m_layoutTimer.isActive()) |
| return; |
| |
| m_delayedLayout = delay.value(); |
| |
| #if !LOG_DISABLED |
| if (!frame().document()->ownerElement()) |
| LOG(Layout, "FrameView %p scheduling layout for %.3fs", this, delay.value()); |
| #endif |
| |
| m_layoutTimer.startOneShot(delay); |
| } |
| |
| void FrameViewLayoutContext::unscheduleLayout() |
| { |
| if (m_asynchronousTasksTimer.isActive()) |
| m_asynchronousTasksTimer.stop(); |
| |
| if (!m_layoutTimer.isActive()) |
| return; |
| |
| #if !LOG_DISABLED |
| if (!frame().document()->ownerElement()) |
| LOG(Layout, "FrameView %p layout timer unscheduled at %.3fs", this, frame().document()->timeSinceDocumentCreation().value()); |
| #endif |
| |
| m_layoutTimer.stop(); |
| m_delayedLayout = false; |
| } |
| |
| void FrameViewLayoutContext::scheduleSubtreeLayout(RenderElement& layoutRoot) |
| { |
| ASSERT(renderView()); |
| auto& renderView = *this->renderView(); |
| |
| // Try to catch unnecessary work during render tree teardown. |
| ASSERT(!renderView.renderTreeBeingDestroyed()); |
| ASSERT(frame().view() == &view()); |
| |
| if (renderView.needsLayout() && !subtreeLayoutRoot()) { |
| layoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No); |
| return; |
| } |
| |
| if (!isLayoutPending() && isLayoutSchedulingEnabled()) { |
| Seconds delay = renderView.document().minimumLayoutDelay(); |
| ASSERT(!layoutRoot.container() || is<RenderView>(layoutRoot.container()) || !layoutRoot.container()->needsLayout()); |
| setSubtreeLayoutRoot(layoutRoot); |
| InspectorInstrumentation::didInvalidateLayout(frame()); |
| m_delayedLayout = delay.value(); |
| m_layoutTimer.startOneShot(delay); |
| return; |
| } |
| |
| auto* subtreeLayoutRoot = this->subtreeLayoutRoot(); |
| if (subtreeLayoutRoot == &layoutRoot) |
| return; |
| |
| if (!subtreeLayoutRoot) { |
| // We already have a pending (full) layout. Just mark the subtree for layout. |
| layoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No); |
| InspectorInstrumentation::didInvalidateLayout(frame()); |
| return; |
| } |
| |
| if (isObjectAncestorContainerOf(*subtreeLayoutRoot, layoutRoot)) { |
| // Keep the current root. |
| layoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No, subtreeLayoutRoot); |
| ASSERT(!subtreeLayoutRoot->container() || is<RenderView>(subtreeLayoutRoot->container()) || !subtreeLayoutRoot->container()->needsLayout()); |
| return; |
| } |
| |
| if (isObjectAncestorContainerOf(layoutRoot, *subtreeLayoutRoot)) { |
| // Re-root at newRelayoutRoot. |
| subtreeLayoutRoot->markContainingBlocksForLayout(ScheduleRelayout::No, &layoutRoot); |
| setSubtreeLayoutRoot(layoutRoot); |
| ASSERT(!layoutRoot.container() || is<RenderView>(layoutRoot.container()) || !layoutRoot.container()->needsLayout()); |
| InspectorInstrumentation::didInvalidateLayout(frame()); |
| return; |
| } |
| // Two disjoint subtrees need layout. Mark both of them and issue a full layout instead. |
| convertSubtreeLayoutToFullLayout(); |
| layoutRoot.markContainingBlocksForLayout(ScheduleRelayout::No); |
| InspectorInstrumentation::didInvalidateLayout(frame()); |
| } |
| |
| void FrameViewLayoutContext::layoutTimerFired() |
| { |
| #if !LOG_DISABLED |
| if (!frame().document()->ownerElement()) |
| LOG(Layout, "FrameView %p layout timer fired at %.3fs", this, frame().document()->timeSinceDocumentCreation().value()); |
| #endif |
| layout(); |
| } |
| |
| RenderElement* FrameViewLayoutContext::subtreeLayoutRoot() const |
| { |
| return m_subtreeLayoutRoot.get(); |
| } |
| |
| void FrameViewLayoutContext::convertSubtreeLayoutToFullLayout() |
| { |
| ASSERT(subtreeLayoutRoot()); |
| subtreeLayoutRoot()->markContainingBlocksForLayout(ScheduleRelayout::No); |
| clearSubtreeLayoutRoot(); |
| } |
| |
| void FrameViewLayoutContext::setSubtreeLayoutRoot(RenderElement& layoutRoot) |
| { |
| m_subtreeLayoutRoot = makeWeakPtr(layoutRoot); |
| } |
| |
| bool FrameViewLayoutContext::canPerformLayout() const |
| { |
| if (isInRenderTreeLayout()) |
| return false; |
| |
| if (layoutDisallowed()) |
| return false; |
| |
| if (view().isPainting()) |
| return false; |
| |
| if (!subtreeLayoutRoot() && !frame().document()->renderView()) |
| return false; |
| |
| return true; |
| } |
| |
| #if ENABLE(TEXT_AUTOSIZING) |
| void FrameViewLayoutContext::applyTextSizingIfNeeded(RenderElement& layoutRoot) |
| { |
| auto& settings = layoutRoot.settings(); |
| bool idempotentMode = settings.textAutosizingUsesIdempotentMode(); |
| if (!settings.textAutosizingEnabled() || idempotentMode || renderView()->printing()) |
| return; |
| auto minimumZoomFontSize = settings.minimumZoomFontSize(); |
| if (!idempotentMode && !minimumZoomFontSize) |
| return; |
| auto textAutosizingWidth = layoutRoot.page().textAutosizingWidth(); |
| if (auto overrideWidth = settings.textAutosizingWindowSizeOverride().width()) |
| textAutosizingWidth = overrideWidth; |
| if (!idempotentMode && !textAutosizingWidth) |
| return; |
| layoutRoot.adjustComputedFontSizesOnBlocks(minimumZoomFontSize, textAutosizingWidth); |
| if (!layoutRoot.needsLayout()) |
| return; |
| LOG(TextAutosizing, "Text Autosizing: minimumZoomFontSize=%.2f textAutosizingWidth=%.2f", minimumZoomFontSize, textAutosizingWidth); |
| layoutRoot.layout(); |
| } |
| #endif |
| |
| void FrameViewLayoutContext::updateStyleForLayout() |
| { |
| Document& document = *frame().document(); |
| |
| // FIXME: This shouldn't be necessary, but see rdar://problem/36670246. |
| if (!document.styleScope().resolverIfExists()) |
| document.styleScope().didChangeStyleSheetEnvironment(); |
| |
| // Viewport-dependent media queries may cause us to need completely different style information. |
| document.styleScope().evaluateMediaQueriesForViewportChange(); |
| |
| document.updateElementsAffectedByMediaQueries(); |
| // If there is any pagination to apply, it will affect the RenderView's style, so we should |
| // take care of that now. |
| view().applyPaginationToViewport(); |
| // 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(); |
| } |
| |
| bool FrameViewLayoutContext::handleLayoutWithFrameFlatteningIfNeeded() |
| { |
| if (!view().isInChildFrameWithFrameFlattening()) |
| return false; |
| |
| startLayoutAtMainFrameViewIfNeeded(); |
| auto* layoutRoot = subtreeLayoutRoot() ? subtreeLayoutRoot() : frame().document()->renderView(); |
| return !layoutRoot || !layoutRoot->needsLayout(); |
| } |
| |
| void FrameViewLayoutContext::startLayoutAtMainFrameViewIfNeeded() |
| { |
| // When we start a layout at the child level as opposed to the topmost frame view and this child |
| // frame requires flattening, we need to re-initiate the layout at the topmost view. Layout |
| // will hit this view eventually. |
| auto* parentView = view().parentFrameView(); |
| if (!parentView) |
| return; |
| |
| // In the middle of parent layout, no need to restart from topmost. |
| if (parentView->layoutContext().isInLayout()) |
| return; |
| |
| // Parent tree is clean. Starting layout from it would have no effect. |
| if (!parentView->needsLayout()) |
| return; |
| |
| while (parentView->parentFrameView()) |
| parentView = parentView->parentFrameView(); |
| |
| LOG(Layout, " frame flattening, starting from root"); |
| parentView->layoutContext().layout(); |
| } |
| |
| LayoutSize FrameViewLayoutContext::layoutDelta() const |
| { |
| if (auto* layoutState = this->layoutState()) |
| return layoutState->layoutDelta(); |
| return { }; |
| } |
| |
| void FrameViewLayoutContext::addLayoutDelta(const LayoutSize& delta) |
| { |
| if (auto* layoutState = this->layoutState()) |
| layoutState->addLayoutDelta(delta); |
| } |
| |
| #if !ASSERT_DISABLED |
| bool FrameViewLayoutContext::layoutDeltaMatches(const LayoutSize& delta) |
| { |
| if (auto* layoutState = this->layoutState()) |
| return layoutState->layoutDeltaMatches(delta); |
| return false; |
| } |
| #endif |
| |
| RenderLayoutState* FrameViewLayoutContext::layoutState() const |
| { |
| if (m_layoutStateStack.isEmpty()) |
| return nullptr; |
| return m_layoutStateStack.last().get(); |
| } |
| |
| void FrameViewLayoutContext::pushLayoutState(RenderElement& root) |
| { |
| ASSERT(!m_paintOffsetCacheDisableCount); |
| ASSERT(!layoutState()); |
| |
| m_layoutStateStack.append(makeUnique<RenderLayoutState>(root)); |
| } |
| |
| bool FrameViewLayoutContext::pushLayoutStateForPaginationIfNeeded(RenderBlockFlow& layoutRoot) |
| { |
| if (layoutState()) |
| return false; |
| m_layoutStateStack.append(makeUnique<RenderLayoutState>(layoutRoot, RenderLayoutState::IsPaginated::Yes)); |
| return true; |
| } |
| |
| bool FrameViewLayoutContext::pushLayoutState(RenderBox& renderer, const LayoutSize& offset, LayoutUnit pageHeight, bool pageHeightChanged) |
| { |
| // We push LayoutState even if layoutState is disabled because it stores layoutDelta too. |
| auto* layoutState = this->layoutState(); |
| if (!layoutState || !needsFullRepaint() || layoutState->isPaginated() || renderer.enclosingFragmentedFlow() |
| || layoutState->lineGrid() || (renderer.style().lineGrid() != RenderStyle::initialLineGrid() && renderer.isRenderBlockFlow())) { |
| m_layoutStateStack.append(makeUnique<RenderLayoutState>(m_layoutStateStack, renderer, offset, pageHeight, pageHeightChanged)); |
| return true; |
| } |
| return false; |
| } |
| |
| void FrameViewLayoutContext::popLayoutState() |
| { |
| m_layoutStateStack.removeLast(); |
| } |
| |
| #ifndef NDEBUG |
| void FrameViewLayoutContext::checkLayoutState() |
| { |
| ASSERT(layoutDeltaMatches(LayoutSize())); |
| ASSERT(!m_paintOffsetCacheDisableCount); |
| } |
| #endif |
| |
| Frame& FrameViewLayoutContext::frame() const |
| { |
| return view().frame(); |
| } |
| |
| FrameView& FrameViewLayoutContext::view() const |
| { |
| return m_frameView; |
| } |
| |
| RenderView* FrameViewLayoutContext::renderView() const |
| { |
| return view().renderView(); |
| } |
| |
| Document* FrameViewLayoutContext::document() const |
| { |
| return frame().document(); |
| } |
| |
| } // namespace WebCore |