/* * Copyright (C) 2018 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. AND ITS CONTRIBUTORS ``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 ITS 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 "LayoutState.h" #if ENABLE(LAYOUT_FORMATTING_CONTEXT) #ifndef NDEBUG #include "BlockFormattingState.h" #include "InlineFormattingState.h" #include "LayoutBox.h" #include "LayoutBoxGeometry.h" #include "LayoutContainerBox.h" #include "LayoutContext.h" #include "LayoutTreeBuilder.h" #include "LegacyInlineTextBox.h" #include "RenderBox.h" #include "RenderInline.h" #include "RenderLineBreak.h" #include "RenderTableCell.h" #include "RenderTableSection.h" #include "RenderView.h" #include namespace WebCore { namespace Layout { static bool areEssentiallyEqual(LayoutUnit a, LayoutUnit b) { if (a == b) return true; // 1/4th CSS pixel. constexpr float epsilon = kFixedPointDenominator / 4; return abs(a.rawValue() - b.rawValue()) <= epsilon; } static bool areEssentiallyEqual(float a, InlineLayoutUnit b) { return areEssentiallyEqual(LayoutUnit { a }, LayoutUnit { b }); } static bool areEssentiallyEqual(LayoutRect a, LayoutRect b) { return areEssentiallyEqual(a.x(), b.x()) && areEssentiallyEqual(a.y(), b.y()) && areEssentiallyEqual(a.width(), b.width()) && areEssentiallyEqual(a.height(), b.height()); } static bool checkForMatchingNonTextRuns(const LineRun& lineRun, const WebCore::LegacyInlineBox& inlineBox) { return areEssentiallyEqual(inlineBox.left(), lineRun.logicalLeft()) && areEssentiallyEqual(inlineBox.right(), lineRun.logicalRight()) && areEssentiallyEqual(inlineBox.top(), lineRun.logicalTop()) && areEssentiallyEqual(inlineBox.bottom(), lineRun.logicalBottom()); } static bool checkForMatchingTextRuns(const LineRun& lineRun, const WebCore::LegacyInlineTextBox& inlineTextBox) { if (!lineRun.text()) return false; return areEssentiallyEqual(inlineTextBox.left(), lineRun.logicalLeft()) && areEssentiallyEqual(inlineTextBox.right(), lineRun.logicalRight()) && areEssentiallyEqual(inlineTextBox.top(), lineRun.logicalTop()) && areEssentiallyEqual(inlineTextBox.bottom(), lineRun.logicalBottom()) && (inlineTextBox.isLineBreak() || (inlineTextBox.start() == lineRun.text()->start() && inlineTextBox.end() == lineRun.text()->end())); } static void collectFlowBoxSubtree(const LegacyInlineFlowBox& flowbox, Vector& inlineBoxes) { auto* inlineBox = flowbox.firstLeafDescendant(); auto* lastLeafDescendant = flowbox.lastLeafDescendant(); while (inlineBox) { inlineBoxes.append(inlineBox); if (inlineBox == lastLeafDescendant) break; inlineBox = inlineBox->nextLeafOnLine(); } } static void collectInlineBoxes(const RenderBlockFlow& root, Vector& inlineBoxes) { for (auto* rootLine = root.firstRootBox(); rootLine; rootLine = rootLine->nextRootBox()) { for (auto* inlineBox = rootLine->firstChild(); inlineBox; inlineBox = inlineBox->nextOnLine()) { if (!is(inlineBox)) { inlineBoxes.append(inlineBox); continue; } collectFlowBoxSubtree(downcast(*inlineBox), inlineBoxes); } } } static bool outputMismatchingComplexLineInformationIfNeeded(TextStream& stream, const LayoutState& layoutState, const RenderBlockFlow& blockFlow, const ContainerBox& inlineFormattingRoot) { auto& inlineFormattingState = layoutState.establishedFormattingState(inlineFormattingRoot); auto& lineRuns = downcast(inlineFormattingState).lineRuns(); // Collect inlineboxes. Vector inlineBoxes; collectInlineBoxes(blockFlow, inlineBoxes); auto mismatched = false; unsigned runIndex = 0; if (inlineBoxes.size() != lineRuns.size()) { stream << "Warning: mismatching number of runs: inlineboxes(" << inlineBoxes.size() << ") vs. inline runs(" << lineRuns.size() << ")"; stream.nextLine(); } for (unsigned inlineBoxIndex = 0; inlineBoxIndex < inlineBoxes.size() && runIndex < lineRuns.size(); ++inlineBoxIndex) { auto& lineRun = lineRuns[runIndex]; auto* inlineBox = inlineBoxes[inlineBoxIndex]; auto* inlineTextBox = is(inlineBox) ? downcast(inlineBox) : nullptr; bool matchingRuns = inlineTextBox ? checkForMatchingTextRuns(lineRun, *inlineTextBox) : checkForMatchingNonTextRuns(lineRun, *inlineBox); if (!matchingRuns) { if (is(inlineBox->renderer())) { //
positioning is weird at this point. It needs proper baseline. matchingRuns = true; ++runIndex; continue; } stream << "Mismatching: run"; if (inlineTextBox) stream << " (" << inlineTextBox->start() << ", " << inlineTextBox->end() << ")"; stream << " (" << inlineBox->logicalLeft() << ", " << inlineBox->logicalTop() << ") (" << inlineBox->logicalWidth() << "x" << inlineBox->logicalHeight() << ")"; stream << " inline run"; if (lineRun.text()) stream << " (" << lineRun.text()->start() << ", " << lineRun.text()->end() << ")"; stream << " (" << lineRun.logicalLeft() << ", " << lineRun.logicalTop() << ") (" << lineRun.logicalWidth() << "x" << lineRun.logicalHeight() << ")"; stream.nextLine(); mismatched = true; } ++runIndex; } return mismatched; } static bool outputMismatchingBlockBoxInformationIfNeeded(TextStream& stream, const LayoutState& layoutState, const RenderBox& renderer, const Box& layoutBox) { bool firstMismatchingRect = true; auto outputRect = [&] (const String& prefix, const LayoutRect& rendererRect, const LayoutRect& layoutRect) { if (firstMismatchingRect) { stream << (renderer.element() ? renderer.element()->nodeName().utf8().data() : "") << " " << renderer.renderName() << "(" << &renderer << ") layoutBox(" << &layoutBox << ")"; stream.nextLine(); firstMismatchingRect = false; } stream << prefix.utf8().data() << "\trenderer->(" << rendererRect.x() << "," << rendererRect.y() << ") (" << rendererRect.width() << "x" << rendererRect.height() << ")" << "\tlayout->(" << layoutRect.x() << "," << layoutRect.y() << ") (" << layoutRect.width() << "x" << layoutRect.height() << ")"; stream.nextLine(); }; auto renderBoxLikeMarginBox = [&] (const auto& boxGeometry) { if (layoutBox.isInitialContainingBlock()) return BoxGeometry::borderBoxRect(boxGeometry); // Produce a RenderBox matching margin box. auto containingBlockWidth = layoutState.geometryForBox(layoutBox.containingBlock()).contentBoxWidth(); auto marginStart = LayoutUnit { }; auto& marginStartStyle = layoutBox.style().marginStart(); if (marginStartStyle.isFixed() || marginStartStyle.isPercent() || marginStartStyle.isCalculated()) marginStart = valueForLength(marginStartStyle, containingBlockWidth); auto marginEnd = LayoutUnit { }; auto& marginEndStyle = layoutBox.style().marginEnd(); if (marginEndStyle.isFixed() || marginEndStyle.isPercent() || marginEndStyle.isCalculated()) marginEnd = valueForLength(marginEndStyle, containingBlockWidth); auto marginBefore = boxGeometry.marginBefore(); auto marginAfter = boxGeometry.marginAfter(); if (layoutBox.formattingContextRoot().establishesBlockFormattingContext()) { auto& formattingState = downcast(layoutState.formattingStateForBox(layoutBox)); auto verticalMargin = formattingState.usedVerticalMargin(layoutBox); marginBefore = verticalMargin.nonCollapsedValues.before; marginAfter = verticalMargin.nonCollapsedValues.after; } auto borderBox = boxGeometry.borderBox(); return Rect { borderBox.top() - marginBefore, borderBox.left() - marginStart, marginStart + borderBox.width() + marginEnd, marginBefore + borderBox.height() + marginAfter }; }; // rendering does not offset for relative positioned boxes. auto frameRect = renderer.frameRect(); if (renderer.isInFlowPositioned()) frameRect.move(renderer.offsetForInFlowPosition()); auto boxGeometry = BoxGeometry { layoutState.geometryForBox(layoutBox) }; if (layoutBox.isTableBox()) { // When the is out-of-flow positioned, the wrapper table box has the offset // while the actual table box is static, inflow. auto& tableWrapperBoxGeometry = layoutState.geometryForBox(layoutBox.containingBlock()); boxGeometry.moveBy(BoxGeometry::borderBoxTopLeft(tableWrapperBoxGeometry)); // Table wrapper box has the margin values for the table. boxGeometry.setHorizontalMargin(tableWrapperBoxGeometry.horizontalMargin()); boxGeometry.setVerticalMargin(tableWrapperBoxGeometry.verticalMargin()); } if (is(renderer) || is(renderer)) { // Table rows and tbody have 0 width for some reason when border collapsing is on. if (is(renderer) && downcast(renderer).table()->collapseBorders()) return false; // Section borders are either collapsed or ignored. However they may produce negative padding boxes. if (is(renderer) && (downcast(renderer).table()->collapseBorders() || renderer.style().hasBorder())) return false; } if (!areEssentiallyEqual(frameRect, BoxGeometry::borderBoxRect(boxGeometry))) { outputRect("frameBox", renderer.frameRect(), BoxGeometry::borderBoxRect(boxGeometry)); return true; } if (!areEssentiallyEqual(renderer.borderBoxRect(), boxGeometry.borderBox())) { outputRect("borderBox", renderer.borderBoxRect(), boxGeometry.borderBox()); return true; } // When the table row border overflows the row, padding box becomes negative and content box is incorrect. auto shouldCheckPaddingAndContentBox = !is(renderer) || renderer.paddingBoxRect().width() >= 0; if (shouldCheckPaddingAndContentBox && !areEssentiallyEqual(renderer.paddingBoxRect(), boxGeometry.paddingBox())) { outputRect("paddingBox", renderer.paddingBoxRect(), boxGeometry.paddingBox()); return true; } auto shouldCheckContentBox = [&] { if (!shouldCheckPaddingAndContentBox) return false; // FIXME: Figure out why trunk/rendering comes back with odd values for and
content box. if (is(renderer) || is(renderer)) return false; // Tables have 0 content box size for some reason when border collapsing is on. return !is(renderer) || !downcast(renderer).collapseBorders(); }(); if (shouldCheckContentBox && !areEssentiallyEqual(renderer.contentBoxRect(), boxGeometry.contentBox())) { outputRect("contentBox", renderer.contentBoxRect(), boxGeometry.contentBox()); return true; } if (!areEssentiallyEqual(renderer.marginBoxRect(), renderBoxLikeMarginBox(boxGeometry))) { // In certain cases, like out-of-flow boxes with margin auto, marginBoxRect() returns 0. It's clearly incorrect, // so let's check the individual margin values instead (and at this point we know that all other boxes match). auto marginsMatch = boxGeometry.marginBefore() == renderer.marginBefore() && boxGeometry.marginAfter() == renderer.marginAfter() && boxGeometry.marginStart() == renderer.marginStart() && boxGeometry.marginEnd() == renderer.marginEnd(); if (!marginsMatch) { outputRect("marginBox", renderer.marginBoxRect(), renderBoxLikeMarginBox(boxGeometry)); return true; } } return false; } static bool verifyAndOutputSubtree(TextStream& stream, const LayoutState& context, const RenderBox& renderer, const Box& layoutBox) { // Rendering code does not have the concept of table wrapper box. Skip it by verifying the first child(table box) instead. if (layoutBox.isTableWrapperBox()) return verifyAndOutputSubtree(stream, context, renderer, *downcast(layoutBox).firstChild()); auto mismtachingGeometry = outputMismatchingBlockBoxInformationIfNeeded(stream, context, renderer, layoutBox); if (!is(layoutBox)) return mismtachingGeometry; auto& containerBox = downcast(layoutBox); auto* childLayoutBox = containerBox.firstChild(); auto* childRenderer = renderer.firstChild(); while (childRenderer) { if (!is(*childRenderer)) { childRenderer = childRenderer->nextSibling(); continue; } if (!childLayoutBox) { stream << "Trees are out of sync!"; stream.nextLine(); return true; } if (is(*childRenderer) && childLayoutBox->establishesInlineFormattingContext()) { ASSERT(childRenderer->childrenInline()); auto mismtachingGeometry = outputMismatchingBlockBoxInformationIfNeeded(stream, context, downcast(*childRenderer), *childLayoutBox); if (mismtachingGeometry) return true; auto& blockFlow = downcast(*childRenderer); auto& formattingRoot = downcast(*childLayoutBox); mismtachingGeometry |= outputMismatchingComplexLineInformationIfNeeded(stream, context, blockFlow, formattingRoot); } else { auto mismatchingSubtreeGeometry = verifyAndOutputSubtree(stream, context, downcast(*childRenderer), *childLayoutBox); mismtachingGeometry |= mismatchingSubtreeGeometry; } childLayoutBox = childLayoutBox->nextSibling(); childRenderer = childRenderer->nextSibling(); } return mismtachingGeometry; } void LayoutContext::verifyAndOutputMismatchingLayoutTree(const LayoutState& layoutState, const RenderView& rootRenderer) { TextStream stream; auto& layoutRoot = layoutState.root(); auto mismatchingGeometry = verifyAndOutputSubtree(stream, layoutState, rootRenderer, layoutRoot); if (!mismatchingGeometry) return; #if ENABLE(TREE_DEBUGGING) showRenderTree(&rootRenderer); showLayoutTree(layoutRoot, &layoutState); #endif WTFLogAlways("%s", stream.release().utf8().data()); ASSERT_NOT_REACHED(); } } } #endif #endif