mirror of
https://github.com/darlinghq/darling-WebCore.git
synced 2024-11-26 22:00:22 +00:00
453 lines
17 KiB
C++
453 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2020 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 "DisplayTreeBuilder.h"
|
|
|
|
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
|
|
|
|
#include "DisplayBoxFactory.h"
|
|
#include "DisplayContainerBox.h"
|
|
#include "DisplayStackingItem.h"
|
|
#include "DisplayStyle.h"
|
|
#include "DisplayTree.h"
|
|
#include "InlineFormattingState.h"
|
|
#include "LayoutBoxGeometry.h"
|
|
#include "LayoutChildIterator.h"
|
|
#include "LayoutContainerBox.h"
|
|
#include "LayoutReplacedBox.h"
|
|
#include "LayoutState.h"
|
|
#include "LayoutTreeBuilder.h" // Just for showLayoutTree.
|
|
#include "Logging.h"
|
|
#include <wtf/IsoMallocInlines.h>
|
|
#include <wtf/text/TextStream.h>
|
|
|
|
namespace WebCore {
|
|
namespace Display {
|
|
|
|
class PositioningContext {
|
|
public:
|
|
PositioningContext(const Display::ContainerBox& rootDisplayBox)
|
|
: m_fixedPositionContainer({ rootDisplayBox, { } })
|
|
, m_absolutePositionContainer({ rootDisplayBox, { } })
|
|
, m_inFlowContainer ({ rootDisplayBox, { } })
|
|
{
|
|
}
|
|
|
|
PositioningContext contextForDescendants(const Layout::Box& layoutBox, const Layout::BoxGeometry geometry, const Display::ContainerBox& displayBox) const
|
|
{
|
|
auto currentOffset = containingBlockContextForLayoutBox(layoutBox).offsetFromRoot;
|
|
|
|
auto borderBoxRect = LayoutRect { Layout::BoxGeometry::borderBoxRect(geometry) };
|
|
currentOffset += toLayoutSize(borderBoxRect.location());
|
|
|
|
auto currentBoxes = ContainingBlockContext { displayBox, currentOffset };
|
|
return {
|
|
layoutBox.isContainingBlockForFixedPosition() ? currentBoxes : m_fixedPositionContainer,
|
|
layoutBox.isContainingBlockForOutOfFlowPosition() ? currentBoxes : m_absolutePositionContainer,
|
|
layoutBox.isContainingBlockForInFlow() ? currentBoxes : m_inFlowContainer
|
|
};
|
|
}
|
|
|
|
const ContainingBlockContext& containingBlockContextForLayoutBox(const Layout::Box& layoutBox) const
|
|
{
|
|
if (layoutBox.isFixedPositioned())
|
|
return m_fixedPositionContainer;
|
|
|
|
if (layoutBox.isOutOfFlowPositioned())
|
|
return m_absolutePositionContainer;
|
|
|
|
return m_inFlowContainer;
|
|
}
|
|
|
|
const ContainingBlockContext& inFlowContainingBlockContext() const { return m_inFlowContainer; }
|
|
|
|
private:
|
|
PositioningContext(const ContainingBlockContext& fixedContainer, const ContainingBlockContext& absoluteContainer, const ContainingBlockContext& inFlowContainer)
|
|
: m_fixedPositionContainer(fixedContainer)
|
|
, m_absolutePositionContainer(absoluteContainer)
|
|
, m_inFlowContainer(inFlowContainer)
|
|
{
|
|
}
|
|
|
|
ContainingBlockContext m_fixedPositionContainer;
|
|
ContainingBlockContext m_absolutePositionContainer;
|
|
ContainingBlockContext m_inFlowContainer;
|
|
};
|
|
|
|
struct BuildingState {
|
|
const ContainerBox& inFlowContainingBlockBox() const { return positioningContext.inFlowContainingBlockContext().box; }
|
|
|
|
PositioningContext positioningContext;
|
|
StackingItem& currentStackingItem;
|
|
StackingItem& currentStackingContextItem;
|
|
|
|
AbsoluteFloatRect currentStackingItemPaintedContentExtent; // This stacking item's contents only.
|
|
AbsoluteFloatRect currentStackingItemPaintingExtent; // Including descendant items.
|
|
};
|
|
|
|
TreeBuilder::TreeBuilder(float pixelSnappingFactor)
|
|
: m_boxFactory(*this, pixelSnappingFactor)
|
|
, m_stateStack(makeUnique<Vector<BuildingState>>())
|
|
{
|
|
m_stateStack->reserveInitialCapacity(32);
|
|
}
|
|
|
|
TreeBuilder::~TreeBuilder() = default;
|
|
|
|
std::unique_ptr<Tree> TreeBuilder::build(const Layout::LayoutState& layoutState)
|
|
{
|
|
ASSERT(layoutState.hasRoot());
|
|
|
|
auto& rootLayoutBox = layoutState.root();
|
|
|
|
#if ENABLE(TREE_DEBUGGING)
|
|
LOG_WITH_STREAM(FormattingContextLayout, stream << "Building display tree for:\n" << layoutTreeAsText(rootLayoutBox, &layoutState));
|
|
#endif
|
|
|
|
ASSERT(!m_tree);
|
|
m_tree = makeUnique<Tree>();
|
|
|
|
m_rootBackgroundPropgation = BoxFactory::determineRootBackgroundPropagation(rootLayoutBox);
|
|
|
|
auto geometry = layoutState.geometryForBox(rootLayoutBox);
|
|
auto rootDisplayBox = m_boxFactory.displayBoxForRootBox(rootLayoutBox, geometry, m_rootBackgroundPropgation);
|
|
|
|
auto insertionPosition = InsertionPosition { *rootDisplayBox };
|
|
auto rootStackingItem = makeUnique<StackingItem>(WTFMove(rootDisplayBox));
|
|
|
|
if (rootLayoutBox.firstChild()) {
|
|
auto& rootContainerBox = downcast<ContainerBox>(rootStackingItem->box());
|
|
m_stateStack->append({ { rootContainerBox }, *rootStackingItem, *rootStackingItem, { }, { } });
|
|
recursiveBuildDisplayTree(layoutState, *rootLayoutBox.firstChild(), insertionPosition);
|
|
}
|
|
|
|
#if ENABLE(TREE_DEBUGGING)
|
|
LOG_WITH_STREAM(FormattingContextLayout, stream << "Display tree:\n" << displayTreeAsText(*rootStackingItem));
|
|
#endif
|
|
m_tree->setRootStackingItem(WTFMove(rootStackingItem));
|
|
return WTFMove(m_tree);
|
|
}
|
|
|
|
void TreeBuilder::pushStateForBoxDescendants(const Layout::ContainerBox& layoutContainerBox, const Layout::BoxGeometry& layoutGeometry, const ContainerBox& displayBox, StackingItem* boxStackingItem)
|
|
{
|
|
auto& positioningContext = m_stateStack->last().positioningContext;
|
|
|
|
auto currentStackingItem = [&]() -> StackingItem& {
|
|
if (boxStackingItem)
|
|
return *boxStackingItem;
|
|
|
|
return m_stateStack->last().currentStackingItem;
|
|
};
|
|
|
|
auto currentStackingContextItem = [&]() -> StackingItem& {
|
|
if (boxStackingItem && boxStackingItem->isStackingContext())
|
|
return *boxStackingItem;
|
|
|
|
return m_stateStack->last().currentStackingContextItem;
|
|
};
|
|
|
|
m_stateStack->append({
|
|
positioningContext.contextForDescendants(layoutContainerBox, layoutGeometry, displayBox),
|
|
currentStackingItem(),
|
|
currentStackingContextItem(),
|
|
{ },
|
|
{ }
|
|
});
|
|
}
|
|
|
|
void TreeBuilder::popState(const BoxModelBox& currentBox)
|
|
{
|
|
ASSERT(m_stateStack->size() > 1);
|
|
auto& currentState = m_stateStack->last();
|
|
auto& previousState = m_stateStack->at(m_stateStack->size() - 2);
|
|
|
|
bool endingStackingItem = ¤tState.currentStackingItem.box() == ¤tBox;
|
|
bool endingStackingContextItem = ¤tState.currentStackingContextItem.box() == ¤tBox;
|
|
|
|
// We've accumulated into currentStackingItemPaintedContentExtent while building descendant boxes.
|
|
auto paintedContentExtent = currentState.currentStackingItemPaintedContentExtent;
|
|
|
|
// Often, but not always, containingBlockBox will be currentBox. If it has overflow clipping, clip the rect from descendants.
|
|
auto& containingBlockBox = currentState.inFlowContainingBlockBox();
|
|
if (containingBlockBox.style().hasClippedOverflow()) {
|
|
auto clipRect = containingBlockBox.absolutePaddingBoxRect();
|
|
paintedContentExtent.intersect(clipRect);
|
|
}
|
|
|
|
// Now union with this box's extent (which is outside the clip).
|
|
auto boxPaintingExtent = unionRect(paintedContentExtent, currentBox.absolutePaintingExtent());
|
|
|
|
if (endingStackingItem) {
|
|
currentState.currentStackingItem.setPaintedContentBounds(boxPaintingExtent);
|
|
|
|
// Now accumulate this item's extent (possibly clipped) into that of its parent item.
|
|
auto descendantItemsPaintingExtent = currentState.currentStackingItemPaintingExtent;
|
|
|
|
if (containingBlockBox.style().hasClippedOverflow()) {
|
|
auto clipRect = containingBlockBox.absolutePaddingBoxRect();
|
|
descendantItemsPaintingExtent.intersect(clipRect);
|
|
}
|
|
|
|
auto itemPaintingExtent = unionRect(descendantItemsPaintingExtent, boxPaintingExtent);
|
|
currentState.currentStackingItem.setPaintedBoundsIncludingDescendantItems(itemPaintingExtent);
|
|
|
|
previousState.currentStackingItemPaintingExtent.unite(itemPaintingExtent);
|
|
} else
|
|
previousState.currentStackingItemPaintedContentExtent.unite(boxPaintingExtent);
|
|
|
|
if (endingStackingContextItem)
|
|
currentState.currentStackingContextItem.sortLists();
|
|
|
|
m_stateStack->removeLast();
|
|
}
|
|
|
|
void TreeBuilder::didAppendNonContainerStackingItem(StackingItem& item)
|
|
{
|
|
// This is s simplified version of pushStateForBoxDescendants/popState.
|
|
auto currentStackingContextItem = [&]() -> StackingItem& {
|
|
if (item.isStackingContext())
|
|
return item;
|
|
|
|
return m_stateStack->last().currentStackingContextItem;
|
|
};
|
|
|
|
m_stateStack->append({
|
|
m_stateStack->last().positioningContext,
|
|
item,
|
|
currentStackingContextItem(),
|
|
{ },
|
|
{ }
|
|
});
|
|
|
|
popState(item.box());
|
|
}
|
|
|
|
void TreeBuilder::accountForBoxPaintingExtent(const Box& box)
|
|
{
|
|
// FIXME: Need to account for transforms.
|
|
currentState().currentStackingItemPaintedContentExtent.unite(box.absolutePaintingExtent());
|
|
}
|
|
|
|
BuildingState& TreeBuilder::currentState()
|
|
{
|
|
ASSERT(m_stateStack && m_stateStack->size());
|
|
return m_stateStack->last();
|
|
}
|
|
|
|
const PositioningContext& TreeBuilder::positioningContext()
|
|
{
|
|
return currentState().positioningContext;
|
|
}
|
|
|
|
void TreeBuilder::insert(std::unique_ptr<Box>&& box, InsertionPosition& insertionPosition) const
|
|
{
|
|
box->setParent(&insertionPosition.container);
|
|
if (insertionPosition.currentChild) {
|
|
auto boxPtr = box.get();
|
|
insertionPosition.currentChild->setNextSibling(WTFMove(box));
|
|
insertionPosition.currentChild = boxPtr;
|
|
} else {
|
|
insertionPosition.currentChild = box.get();
|
|
insertionPosition.container.setFirstChild(WTFMove(box));
|
|
}
|
|
}
|
|
|
|
StackingItem* TreeBuilder::insertIntoTree(std::unique_ptr<Box>&& box, InsertionPosition& insertionPosition, WillTraverseDescendants willTraverseDescendants)
|
|
{
|
|
if (box->participatesInZOrderSorting() && is<BoxModelBox>(*box)) {
|
|
auto boxModelBox = std::unique_ptr<BoxModelBox> { downcast<BoxModelBox>(box.release()) };
|
|
auto stackingItem = makeUnique<StackingItem>(WTFMove(boxModelBox));
|
|
|
|
// This painted bounds will get extended as we traverse box descendants.
|
|
stackingItem->setPaintedContentBounds(stackingItem->box().absolutePaintingExtent());
|
|
|
|
auto* stackingItemPtr = stackingItem.get();
|
|
currentState().currentStackingContextItem.addChildStackingItem(WTFMove(stackingItem));
|
|
|
|
if (willTraverseDescendants == WillTraverseDescendants::No)
|
|
didAppendNonContainerStackingItem(*stackingItemPtr);
|
|
|
|
return stackingItemPtr;
|
|
}
|
|
|
|
insert(WTFMove(box), insertionPosition);
|
|
|
|
if (willTraverseDescendants == WillTraverseDescendants::No)
|
|
accountForBoxPaintingExtent(*insertionPosition.currentChild);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void TreeBuilder::buildInlineDisplayTree(const Layout::LayoutState& layoutState, const Layout::ContainerBox& inlineFormattingRoot, InsertionPosition& insertionPosition)
|
|
{
|
|
auto& inlineFormattingState = layoutState.establishedInlineFormattingState(inlineFormattingRoot);
|
|
|
|
for (auto& run : inlineFormattingState.lineRuns()) {
|
|
if (run.text()) {
|
|
auto& lineGeometry = inlineFormattingState.lines().at(run.lineIndex());
|
|
auto textBox = m_boxFactory.displayBoxForTextRun(run, lineGeometry, positioningContext().inFlowContainingBlockContext());
|
|
insert(WTFMove(textBox), insertionPosition);
|
|
accountForBoxPaintingExtent(*insertionPosition.currentChild);
|
|
continue;
|
|
}
|
|
|
|
if (is<Layout::ContainerBox>(run.layoutBox())) {
|
|
recursiveBuildDisplayTree(layoutState, run.layoutBox(), insertionPosition);
|
|
continue;
|
|
}
|
|
|
|
// FIXME: Workaround for webkit.orgb/b/219335.
|
|
if (run.layoutBox().isLineBreakBox() && run.layoutBox().isOutOfFlowPositioned())
|
|
continue;
|
|
|
|
auto geometry = layoutState.geometryForBox(run.layoutBox());
|
|
auto displayBox = m_boxFactory.displayBoxForLayoutBox(run.layoutBox(), geometry, positioningContext().inFlowContainingBlockContext());
|
|
insertIntoTree(WTFMove(displayBox), insertionPosition, WillTraverseDescendants::No);
|
|
}
|
|
}
|
|
|
|
void TreeBuilder::recursiveBuildDisplayTree(const Layout::LayoutState& layoutState, const Layout::Box& layoutBox, InsertionPosition& insertionPosition)
|
|
{
|
|
auto geometry = layoutState.geometryForBox(layoutBox);
|
|
std::unique_ptr<Box> displayBox;
|
|
|
|
auto& containingBlockContext = positioningContext().containingBlockContextForLayoutBox(layoutBox);
|
|
if (layoutBox.isBodyBox())
|
|
displayBox = m_boxFactory.displayBoxForBodyBox(layoutBox, geometry, containingBlockContext, m_rootBackgroundPropgation);
|
|
else
|
|
displayBox = m_boxFactory.displayBoxForLayoutBox(layoutBox, geometry, containingBlockContext);
|
|
|
|
Box& currentBox = *displayBox;
|
|
|
|
auto willTraverseDescendants = (is<Layout::ContainerBox>(layoutBox) && downcast<Layout::ContainerBox>(layoutBox).hasChild()) ? WillTraverseDescendants::Yes : WillTraverseDescendants::No;
|
|
auto* stackingItem = insertIntoTree(WTFMove(displayBox), insertionPosition, willTraverseDescendants);
|
|
if (willTraverseDescendants == WillTraverseDescendants::No)
|
|
return;
|
|
|
|
auto& layoutContainerBox = downcast<Layout::ContainerBox>(layoutBox);
|
|
|
|
ContainerBox& currentContainerBox = downcast<ContainerBox>(currentBox);
|
|
pushStateForBoxDescendants(layoutContainerBox, geometry, currentContainerBox, stackingItem);
|
|
|
|
auto insertionPositionForChildren = InsertionPosition { currentContainerBox };
|
|
|
|
enum class DescendantBoxInclusion { AllBoxes, OutOfFlowOnly };
|
|
auto boxInclusion = DescendantBoxInclusion::AllBoxes;
|
|
|
|
if (layoutContainerBox.establishesInlineFormattingContext()) {
|
|
buildInlineDisplayTree(layoutState, downcast<Layout::ContainerBox>(layoutContainerBox), insertionPositionForChildren);
|
|
boxInclusion = DescendantBoxInclusion::OutOfFlowOnly;
|
|
}
|
|
|
|
auto includeBox = [](DescendantBoxInclusion boxInclusion, const Layout::Box& box) {
|
|
switch (boxInclusion) {
|
|
case DescendantBoxInclusion::AllBoxes: return true;
|
|
case DescendantBoxInclusion::OutOfFlowOnly: return !box.isInFlow();
|
|
}
|
|
return false;
|
|
};
|
|
|
|
for (auto& child : Layout::childrenOfType<Layout::Box>(layoutContainerBox)) {
|
|
if (includeBox(boxInclusion, child) && layoutState.hasBoxGeometry(child))
|
|
recursiveBuildDisplayTree(layoutState, child, insertionPositionForChildren);
|
|
}
|
|
|
|
popState(currentContainerBox);
|
|
}
|
|
|
|
#if ENABLE(TREE_DEBUGGING)
|
|
|
|
static void outputDisplayBox(TextStream& stream, const Box& displayBox, bool writeIndent = true)
|
|
{
|
|
if (writeIndent)
|
|
stream.writeIndent();
|
|
|
|
stream << displayBox.debugDescription();
|
|
stream << "\n";
|
|
}
|
|
|
|
static void outputDisplayTree(TextStream& stream, const Box& displayBox, bool writeFirstItemIndent = true)
|
|
{
|
|
outputDisplayBox(stream, displayBox, writeFirstItemIndent);
|
|
|
|
if (is<ContainerBox>(displayBox)) {
|
|
TextStream::IndentScope indent(stream);
|
|
for (auto child = downcast<ContainerBox>(displayBox).firstChild(); child; child = child->nextSibling())
|
|
outputDisplayTree(stream, *child);
|
|
}
|
|
}
|
|
|
|
String displayTreeAsText(const Box& box)
|
|
{
|
|
TextStream stream(TextStream::LineMode::MultipleLine, TextStream::Formatting::SVGStyleRect);
|
|
outputDisplayTree(stream, box);
|
|
return stream.release();
|
|
}
|
|
|
|
void showDisplayTree(const Box& box)
|
|
{
|
|
auto treeAsText = displayTreeAsText(box);
|
|
WTFLogAlways("%s", treeAsText.utf8().data());
|
|
}
|
|
|
|
static void outputStackingTree(TextStream& stream, const char* prefix, const StackingItem& stackingItem)
|
|
{
|
|
stream.writeIndent();
|
|
stream << "[" << prefix << "] item (" << &stackingItem << ") " << stackingItem.paintedContentBounds() << " - ";
|
|
|
|
TextStream::IndentScope indent(stream);
|
|
outputDisplayTree(stream, stackingItem.box(), false);
|
|
|
|
{
|
|
TextStream::IndentScope indent(stream);
|
|
for (auto& childStackingItem : stackingItem.negativeZOrderList())
|
|
outputStackingTree(stream, "-", *childStackingItem);
|
|
|
|
for (auto& childStackingItem : stackingItem.positiveZOrderList())
|
|
outputStackingTree(stream, childStackingItem->isStackingContext() ? "+" : "p", *childStackingItem);
|
|
}
|
|
}
|
|
|
|
String displayTreeAsText(const StackingItem& stackingItem)
|
|
{
|
|
TextStream stream(TextStream::LineMode::MultipleLine);
|
|
outputStackingTree(stream, "+", stackingItem);
|
|
return stream.release();
|
|
}
|
|
|
|
void showDisplayTree(const StackingItem& stackingItem)
|
|
{
|
|
auto treeAsText = displayTreeAsText(stackingItem);
|
|
WTFLogAlways("%s", treeAsText.utf8().data());
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace Display
|
|
} // namespace WebCore
|
|
|
|
#endif // ENABLE(LAYOUT_FORMATTING_CONTEXT)
|