blob: f541ce24abaf8da7222edadf2843ec4d2f0e9b43 [file] [log] [blame]
/*
* Copyright (C) 2020-2022 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 "LayoutIntegrationBoxTree.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "InlineWalker.h"
#include "LayoutContainerBox.h"
#include "LayoutInlineTextBox.h"
#include "LayoutLineBreakBox.h"
#include "LayoutReplacedBox.h"
#include "RenderBlockFlow.h"
#include "RenderChildIterator.h"
#include "RenderImage.h"
#include "RenderLineBreak.h"
#include "TextUtil.h"
#if ENABLE(TREE_DEBUGGING)
#include "LayoutIntegrationLineLayout.h"
#endif
namespace WebCore {
namespace LayoutIntegration {
static constexpr size_t smallTreeThreshold = 8;
// FIXME: see webkit.org/b/230964
#define CAN_USE_FIRST_LINE_STYLE_RESOLVE 1
static RenderStyle rootBoxStyle(const RenderStyle& style)
{
auto clonedStyle = RenderStyle::clone(style);
clonedStyle.setEffectiveDisplay(DisplayType::Block);
return clonedStyle;
}
static std::unique_ptr<RenderStyle> rootBoxFirstLineStyle(const RenderBlockFlow& root)
{
#if CAN_USE_FIRST_LINE_STYLE_RESOLVE
auto& firstLineStyle = root.firstLineStyle();
if (root.style() == firstLineStyle)
return { };
auto clonedStyle = RenderStyle::clonePtr(firstLineStyle);
clonedStyle->setEffectiveDisplay(DisplayType::Block);
return clonedStyle;
#else
UNUSED_PARAM(root);
return { };
#endif
}
BoxTree::BoxTree(RenderBlockFlow& flow)
: m_flow(flow)
, m_root(Layout::Box::ElementAttributes { }, rootBoxStyle(flow.style()), rootBoxFirstLineStyle(flow))
{
m_root.setIsIntegrationBlockContainer();
if (flow.isAnonymous())
m_root.setIsAnonymous();
buildTree();
}
void BoxTree::buildTree()
{
auto createChildBox = [&](RenderObject& childRenderer) -> std::unique_ptr<Layout::Box> {
std::unique_ptr<RenderStyle> firstLineStyle;
#if CAN_USE_FIRST_LINE_STYLE_RESOLVE
if (&childRenderer.style() != &childRenderer.firstLineStyle())
firstLineStyle = RenderStyle::clonePtr(childRenderer.firstLineStyle());
#endif
if (is<RenderText>(childRenderer)) {
auto& textRenderer = downcast<RenderText>(childRenderer);
auto style = RenderStyle::createAnonymousStyleWithDisplay(textRenderer.style(), DisplayType::Inline);
auto text = style.textSecurity() == TextSecurity::None ? textRenderer.text() : RenderBlock::updateSecurityDiscCharacters(style, textRenderer.text());
auto useSimplifiedTextMeasuring = textRenderer.canUseSimplifiedTextMeasuring() && (!firstLineStyle || firstLineStyle->fontCascade() == style.fontCascade());
return makeUnique<Layout::InlineTextBox>(text, useSimplifiedTextMeasuring, textRenderer.canUseSimpleFontCodePath(), WTFMove(style), WTFMove(firstLineStyle));
}
auto style = RenderStyle::clone(childRenderer.style());
if (childRenderer.isLineBreak()) {
auto adjustStyle = [&] (auto& styleToAdjust) {
styleToAdjust.setDisplay(DisplayType::Inline);
styleToAdjust.setFloating(Float::None);
styleToAdjust.setPosition(PositionType::Static);
};
adjustStyle(style);
if (firstLineStyle)
adjustStyle(*firstLineStyle);
return makeUnique<Layout::LineBreakBox>(downcast<RenderLineBreak>(childRenderer).isWBR(), WTFMove(style), WTFMove(firstLineStyle));
}
if (is<RenderReplaced>(childRenderer))
return makeUnique<Layout::ReplacedBox>(Layout::Box::ElementAttributes { is<RenderImage>(childRenderer) ? Layout::Box::ElementType::Image : Layout::Box::ElementType::GenericElement }, WTFMove(style), WTFMove(firstLineStyle));
if (is<RenderBlock>(childRenderer))
return makeUnique<Layout::ReplacedBox>(Layout::Box::ElementAttributes { Layout::Box::ElementType::GenericElement }, WTFMove(style), WTFMove(firstLineStyle));
if (is<RenderInline>(childRenderer)) {
// This looks like continuation renderer.
auto& renderInline = downcast<RenderInline>(childRenderer);
auto shouldNotRetainBorderPaddingAndMarginStart = renderInline.isContinuation();
auto shouldNotRetainBorderPaddingAndMarginEnd = !renderInline.isContinuation() && renderInline.inlineContinuation();
auto adjustStyleForContinuation = [&] (auto& styleToAdjust) {
if (shouldNotRetainBorderPaddingAndMarginStart) {
styleToAdjust.setMarginStart(RenderStyle::initialMargin());
styleToAdjust.resetBorderLeft();
styleToAdjust.setPaddingLeft(RenderStyle::initialPadding());
}
if (shouldNotRetainBorderPaddingAndMarginEnd) {
styleToAdjust.setMarginEnd(RenderStyle::initialMargin());
styleToAdjust.resetBorderRight();
styleToAdjust.setPaddingRight(RenderStyle::initialPadding());
}
};
adjustStyleForContinuation(style);
if (firstLineStyle)
adjustStyleForContinuation(*firstLineStyle);
return makeUnique<Layout::ContainerBox>(Layout::Box::ElementAttributes { Layout::Box::ElementType::GenericElement }, WTFMove(style), WTFMove(firstLineStyle));
}
ASSERT_NOT_REACHED();
return nullptr;
};
for (auto walker = InlineWalker(m_flow); !walker.atEnd(); walker.advance()) {
auto& childRenderer = *walker.current();
auto childBox = createChildBox(childRenderer);
appendChild(makeUniqueRefFromNonNullUniquePtr(WTFMove(childBox)), childRenderer);
}
}
void BoxTree::appendChild(UniqueRef<Layout::Box> childBox, RenderObject& childRenderer)
{
auto& parentBox = downcast<Layout::ContainerBox>(layoutBoxForRenderer(*childRenderer.parent()));
m_boxes.append({ childBox.get(), &childRenderer });
parentBox.appendChild(WTFMove(childBox));
if (m_boxes.size() > smallTreeThreshold) {
if (m_rendererToBoxMap.isEmpty()) {
for (auto& entry : m_boxes)
m_rendererToBoxMap.add(entry.renderer, entry.box.get());
} else
m_rendererToBoxMap.add(&childRenderer, m_boxes.last().box.get());
}
}
void BoxTree::updateStyle(const RenderBoxModelObject& renderer)
{
auto& layoutBox = layoutBoxForRenderer(renderer);
auto& style = renderer.style();
auto firstLineStyle = [&] () -> std::unique_ptr<RenderStyle> {
#if CAN_USE_FIRST_LINE_STYLE_RESOLVE
if (&renderer.style() != &renderer.firstLineStyle())
return RenderStyle::clonePtr(renderer.firstLineStyle());
return nullptr;
#else
return nullptr;
#endif
};
if (&layoutBox == &rootLayoutBox())
layoutBox.updateStyle(rootBoxStyle(style), rootBoxFirstLineStyle(downcast<RenderBlockFlow>(renderer)));
else
layoutBox.updateStyle(style, firstLineStyle());
if (is<Layout::ContainerBox>(layoutBox)) {
for (auto* child = downcast<Layout::ContainerBox>(layoutBox).firstChild(); child; child = child->nextSibling()) {
if (child->isInlineTextBox())
child->updateStyle(RenderStyle::createAnonymousStyleWithDisplay(style, DisplayType::Inline), firstLineStyle());
}
}
}
Layout::Box& BoxTree::layoutBoxForRenderer(const RenderObject& renderer)
{
if (&renderer == &m_flow)
return m_root;
if (m_boxes.size() <= smallTreeThreshold) {
auto index = m_boxes.findIf([&](auto& entry) {
return entry.renderer == &renderer;
});
RELEASE_ASSERT(index != notFound);
return m_boxes[index].box;
}
return *m_rendererToBoxMap.get(&renderer);
}
const Layout::Box& BoxTree::layoutBoxForRenderer(const RenderObject& renderer) const
{
return const_cast<BoxTree&>(*this).layoutBoxForRenderer(renderer);
}
RenderObject& BoxTree::rendererForLayoutBox(const Layout::Box& box)
{
if (&box == &m_root)
return m_flow;
if (m_boxes.size() <= smallTreeThreshold) {
auto index = m_boxes.findIf([&](auto& entry) {
return entry.box.ptr() == &box;
});
RELEASE_ASSERT(index != notFound);
return *m_boxes[index].renderer;
}
if (m_boxToRendererMap.isEmpty()) {
for (auto& entry : m_boxes)
m_boxToRendererMap.add(entry.box.get(), entry.renderer);
}
return *m_boxToRendererMap.get(&box);
}
const RenderObject& BoxTree::rendererForLayoutBox(const Layout::Box& box) const
{
return const_cast<BoxTree&>(*this).rendererForLayoutBox(box);
}
#if ENABLE(TREE_DEBUGGING)
void showInlineContent(TextStream& stream, const InlineContent& inlineContent, size_t depth)
{
auto& lines = inlineContent.lines;
auto& boxes = inlineContent.boxes;
for (size_t lineIndex = 0; lineIndex < lines.size(); ++lineIndex) {
auto addSpacing = [&] {
size_t printedCharacters = 0;
stream << "-------- --";
while (++printedCharacters <= depth * 2)
stream << " ";
};
addSpacing();
auto& line = lines[lineIndex];
stream << "line at (" << line.lineBoxLeft() << "," << line.lineBoxTop() << ") size (" << line.lineBoxRight() - line.lineBoxLeft() << "x" << line.lineBoxBottom() - line.lineBoxTop() << ") baseline (" << line.baseline() << ") enclosing top (" << line.enclosingContentTop() << ") bottom (" << line.enclosingContentBottom() << ")";
stream.nextLine();
addSpacing();
stream << " Inline level boxes:";
stream.nextLine();
auto outputInlineLevelBox = [&](const auto& inlineLevelBox) {
addSpacing();
stream << " ";
auto rect = inlineLevelBox.rect();
auto& layoutBox = inlineLevelBox.layoutBox();
if (layoutBox.isAtomicInlineLevelBox())
stream << "Atomic inline level box";
else if (layoutBox.isLineBreakBox())
stream << "Line break box";
else if (layoutBox.isInlineBox())
stream << "Inline box";
else
stream << "Generic inline level box";
stream
<< " at (" << rect.x() << "," << rect.y() << ")"
<< " size (" << rect.width() << "x" << rect.height() << ")";
stream.nextLine();
};
for (auto& box : boxes) {
if (box.lineIndex() != lineIndex)
continue;
if (!box.layoutBox().isInlineLevelBox())
continue;
outputInlineLevelBox(box);
}
addSpacing();
stream << " Runs:";
stream.nextLine();
for (auto& box : boxes) {
if (box.lineIndex() != lineIndex)
continue;
addSpacing();
stream << " ";
if (box.text())
stream << "text box";
else
stream << "box box";
stream << " at (" << box.left() << "," << box.top() << ") size " << box.width() << "x" << box.height();
if (box.text())
stream << " box(" << box.text()->start() << ", " << box.text()->end() << ")";
stream.nextLine();
}
}
}
#endif
}
}
#endif