blob: 47506acd33b707435e59e165cc60703fe64e46e6 [file] [log] [blame]
/*
* Copyright (C) 2012 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 IN..0TERRUPTION) 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 "RenderNamedFlowThread.h"
#include "ComposedTreeAncestorIterator.h"
#include "FlowThreadController.h"
#include "InlineTextBox.h"
#include "InspectorInstrumentation.h"
#include "NodeTraversal.h"
#include "Position.h"
#include "Range.h"
#include "RenderInline.h"
#include "RenderLayer.h"
#include "RenderLineBreak.h"
#include "RenderNamedFlowFragment.h"
#include "RenderText.h"
#include "RenderView.h"
#include "ShadowRoot.h"
#include "Text.h"
#include "WebKitNamedFlow.h"
namespace WebCore {
RenderNamedFlowThread::RenderNamedFlowThread(Document& document, RenderStyle&& style, Ref<WebKitNamedFlow>&& namedFlow)
: RenderFlowThread(document, WTFMove(style))
, m_hasRegionsWithStyling(false)
, m_dispatchRegionOversetChangeEvent(false)
, m_namedFlow(WTFMove(namedFlow))
, m_regionOversetChangeEventTimer(*this, &RenderNamedFlowThread::regionOversetChangeEventTimerFired)
{
}
RenderNamedFlowThread::~RenderNamedFlowThread()
{
// The flow thread can be destroyed without unregistering the content nodes if the document is destroyed.
// This can lead to problems because the nodes are still marked as belonging to a flow thread.
clearContentElements();
// Also leave the NamedFlow object in a consistent state by calling mark for destruction.
setMarkForDestruction();
}
const char* RenderNamedFlowThread::renderName() const
{
return "RenderNamedFlowThread";
}
void RenderNamedFlowThread::clearContentElements()
{
for (auto& contentElement : m_contentElements) {
ASSERT(contentElement);
ASSERT(contentElement->isNamedFlowContentElement());
ASSERT(&contentElement->document() == &document());
contentElement->clearIsNamedFlowContentElement();
}
m_contentElements.clear();
}
void RenderNamedFlowThread::updateWritingMode()
{
auto* firstFragment = downcast<RenderNamedFlowFragment>(m_regionList.first());
if (!firstFragment)
return;
if (style().writingMode() == firstFragment->style().writingMode())
return;
// The first region defines the principal writing mode for the entire flow.
auto newStyle = RenderStyle::clone(style());
newStyle.setWritingMode(firstFragment->style().writingMode());
setStyle(WTFMove(newStyle));
}
bool RenderNamedFlowThread::dependsOn(RenderNamedFlowThread* otherRenderFlowThread) const
{
if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread))
return true;
// Recursively traverse the m_layoutBeforeThreadsSet.
for (const auto& beforeFlowThreadPair : m_layoutBeforeThreadsSet) {
const auto& beforeFlowThread = beforeFlowThreadPair.key;
if (beforeFlowThread->dependsOn(otherRenderFlowThread))
return true;
}
return false;
}
// Compare two regions to determine in which one the content should flow first.
// The function returns true if the first passed region is "less" than the second passed region.
// If the first region appears before second region in DOM,
// the first region is "less" than the second region.
// If the first region is "less" than the second region, the first region receives content before second region.
static bool compareRenderNamedFlowFragments(const RenderNamedFlowFragment* firstFragment, const RenderNamedFlowFragment* secondFragment)
{
ASSERT(firstFragment);
ASSERT(secondFragment);
ASSERT(firstFragment->generatingElement());
ASSERT(secondFragment->generatingElement());
// If the regions belong to different nodes, compare their position in the DOM.
if (firstFragment->generatingElement() != secondFragment->generatingElement()) {
unsigned short position = firstFragment->generatingElement()->compareDocumentPosition(*secondFragment->generatingElement());
// If the second region is contained in the first one, the first region is "less" if it's :before.
if (position & Node::DOCUMENT_POSITION_CONTAINED_BY) {
ASSERT(secondFragment->style().styleType() == NOPSEUDO);
return firstFragment->style().styleType() == BEFORE;
}
// If the second region contains the first region, the first region is "less" if the second is :after.
if (position & Node::DOCUMENT_POSITION_CONTAINS) {
ASSERT(firstFragment->style().styleType() == NOPSEUDO);
return secondFragment->style().styleType() == AFTER;
}
return (position & Node::DOCUMENT_POSITION_FOLLOWING);
}
// FIXME: Currently it's not possible for an element to be both a region and have pseudo-children. The case is covered anyway.
switch (firstFragment->style().styleType()) {
case BEFORE:
// The second region can be the node or the after pseudo-element (before is smaller than any of those).
return true;
case AFTER:
// The second region can be the node or the before pseudo-element (after is greater than any of those).
return false;
case NOPSEUDO:
// The second region can either be the before or the after pseudo-element (the node is only smaller than the after pseudo-element).
return firstFragment->style().styleType() == AFTER;
default:
break;
}
ASSERT_NOT_REACHED();
return true;
}
// This helper function adds a region to a list preserving the order property of the list.
static void addFragmentToList(RenderRegionList& regionList, RenderNamedFlowFragment* renderNamedFlowFragment)
{
if (regionList.isEmpty())
regionList.add(renderNamedFlowFragment);
else {
// Find the first region "greater" than renderNamedFlowFragment.
auto it = regionList.begin();
while (it != regionList.end() && !compareRenderNamedFlowFragments(renderNamedFlowFragment, downcast<RenderNamedFlowFragment>(*it)))
++it;
regionList.insertBefore(it, renderNamedFlowFragment);
}
}
void RenderNamedFlowThread::addFragmentToNamedFlowThread(RenderNamedFlowFragment* renderNamedFlowFragment)
{
ASSERT(renderNamedFlowFragment);
ASSERT(!renderNamedFlowFragment->isValid());
if (renderNamedFlowFragment->parentNamedFlowThread())
addDependencyOnFlowThread(renderNamedFlowFragment->parentNamedFlowThread());
renderNamedFlowFragment->setIsValid(true);
renderNamedFlowFragment->updateRegionFlags();
addFragmentToList(m_regionList, renderNamedFlowFragment);
if (m_regionList.first() == renderNamedFlowFragment)
updateWritingMode();
}
void RenderNamedFlowThread::addRegionToThread(RenderRegion* renderRegion)
{
ASSERT(renderRegion);
ASSERT(!renderRegion->isValid());
RenderNamedFlowFragment& renderNamedFlowFragment = downcast<RenderNamedFlowFragment>(*renderRegion);
resetMarkForDestruction();
if (renderNamedFlowFragment.parentNamedFlowThread() && renderNamedFlowFragment.parentNamedFlowThread()->dependsOn(this)) {
// The order of invalid regions is irrelevant.
m_invalidRegionList.add(&renderNamedFlowFragment);
// Register ourself to get a notification when the state changes.
renderNamedFlowFragment.parentNamedFlowThread()->m_observerThreadsSet.add(this);
return;
}
addFragmentToNamedFlowThread(&renderNamedFlowFragment);
invalidateRegions();
}
void RenderNamedFlowThread::removeRegionFromThread(RenderRegion* renderRegion)
{
ASSERT(renderRegion);
RenderNamedFlowFragment& renderNamedFlowFragment = downcast<RenderNamedFlowFragment>(*renderRegion);
if (renderNamedFlowFragment.parentNamedFlowThread()) {
if (!renderNamedFlowFragment.isValid()) {
ASSERT(m_invalidRegionList.contains(&renderNamedFlowFragment));
m_invalidRegionList.remove(&renderNamedFlowFragment);
renderNamedFlowFragment.parentNamedFlowThread()->m_observerThreadsSet.remove(this);
// No need to invalidate the regions rectangles. The removed region
// was not taken into account. Just return here.
return;
}
removeDependencyOnFlowThread(renderNamedFlowFragment.parentNamedFlowThread());
}
ASSERT(m_regionList.contains(&renderNamedFlowFragment));
bool wasFirst = m_regionList.first() == &renderNamedFlowFragment;
m_regionList.remove(&renderNamedFlowFragment);
if (canBeDestroyed())
setMarkForDestruction();
if (!m_regionList.isEmpty() && wasFirst)
updateWritingMode();
invalidateRegions();
}
void RenderNamedFlowThread::regionChangedWritingMode(RenderRegion* region)
{
if (m_regionList.first() == region)
updateWritingMode();
}
LayoutRect RenderNamedFlowThread::decorationsClipRectForBoxInNamedFlowFragment(const RenderBox& box, RenderNamedFlowFragment& fragment) const
{
LayoutRect visualOverflowRect = fragment.visualOverflowRectForBox(box);
LayoutUnit initialLogicalX = style().isHorizontalWritingMode() ? visualOverflowRect.x() : visualOverflowRect.y();
// The visual overflow rect returned by visualOverflowRectForBox is already flipped but the
// RenderRegion::rectFlowPortionForBox method expects it unflipped.
flipForWritingModeLocalCoordinates(visualOverflowRect);
visualOverflowRect = fragment.rectFlowPortionForBox(&box, visualOverflowRect);
// Now flip it again.
flipForWritingModeLocalCoordinates(visualOverflowRect);
// Take the scrolled offset of this object's parents into consideration.
ScrollPosition scrollPosition;
RenderBlock* containingBlock = box.containingBlock();
while (containingBlock && !is<RenderView>(*containingBlock)) {
if (containingBlock->isRenderNamedFlowThread()) {
// We've reached the flow thread, take the scrolled offset of the region into consideration.
ASSERT(containingBlock == this);
scrollPosition += toIntSize(fragment.fragmentContainer().scrollPosition());
break;
}
scrollPosition += toIntSize(containingBlock->scrollPosition());
containingBlock = containingBlock->containingBlock();
}
if (!scrollPosition.isZero()) {
if (style().isFlippedBlocksWritingMode())
scrollPosition = -scrollPosition;
visualOverflowRect.inflateX(scrollPosition.x());
visualOverflowRect.inflateY(scrollPosition.y());
}
// Layers are in physical coordinates so the origin must be moved to the physical top-left of the flowthread.
if (style().isFlippedBlocksWritingMode()) {
if (style().isHorizontalWritingMode())
visualOverflowRect.moveBy(LayoutPoint(0, height()));
else
visualOverflowRect.moveBy(LayoutPoint(width(), 0));
}
const RenderBox* iterBox = &box;
while (iterBox && iterBox != this) {
RenderBlock* containerBlock = iterBox->containingBlock();
// FIXME: This doesn't work properly with flipped writing modes.
// https://bugs.webkit.org/show_bug.cgi?id=125149
if (iterBox->isPositioned()) {
// For positioned elements, just use the layer's absolute bounding box.
visualOverflowRect.moveBy(iterBox->layer()->absoluteBoundingBox().location());
break;
}
LayoutRect currentBoxRect = iterBox->frameRect();
if (iterBox->style().isFlippedBlocksWritingMode()) {
if (iterBox->style().isHorizontalWritingMode())
currentBoxRect.setY(currentBoxRect.height() - currentBoxRect.maxY());
else
currentBoxRect.setX(currentBoxRect.width() - currentBoxRect.maxX());
}
if (containerBlock->style().writingMode() != iterBox->style().writingMode())
iterBox->flipForWritingMode(currentBoxRect);
visualOverflowRect.moveBy(currentBoxRect.location());
iterBox = containerBlock;
}
// Since the purpose of this method is to make sure the borders of a fragmented
// element don't overflow the region in the fragmentation direction, there's no
// point in restricting the clipping rect on the logical X axis.
// This also saves us the trouble of handling percent-based widths and margins
// since the absolute bounding box of a positioned element would not contain
// the correct coordinates relative to the region we're interested in, but rather
// relative to the actual flow thread.
if (style().isHorizontalWritingMode()) {
if (initialLogicalX < visualOverflowRect.x())
visualOverflowRect.shiftXEdgeTo(initialLogicalX);
if (visualOverflowRect.width() < frameRect().width())
visualOverflowRect.setWidth(frameRect().width());
} else {
if (initialLogicalX < visualOverflowRect.y())
visualOverflowRect.shiftYEdgeTo(initialLogicalX);
if (visualOverflowRect.height() < frameRect().height())
visualOverflowRect.setHeight(frameRect().height());
}
return visualOverflowRect;
}
RenderBlock* RenderNamedFlowThread::fragmentFromRenderBoxAsRenderBlock(RenderBox* renderBox, const IntPoint& absolutePoint, const RenderBox& flowedBox)
{
return downcast<RenderNamedFlowThread>(*renderBox).fragmentFromAbsolutePointAndBox(absolutePoint, flowedBox);
}
RenderNamedFlowFragment* RenderNamedFlowThread::fragmentFromAbsolutePointAndBox(const IntPoint& absolutePoint, const RenderBox& flowedBox)
{
RenderRegion* startRegion = nullptr;
RenderRegion* endRegion = nullptr;
if (!getRegionRangeForBox(&flowedBox, startRegion, endRegion))
return nullptr;
for (auto iter = m_regionList.find(startRegion), end = m_regionList.end(); iter != end; ++iter) {
auto& fragment = downcast<RenderNamedFlowFragment>(**iter);
RenderBlockFlow& fragmentContainer = fragment.fragmentContainer();
IntRect fragmentAbsoluteRect(roundedIntPoint(fragmentContainer.localToAbsolute()), roundedIntSize(fragmentContainer.paddingBoxRect().size()));
if (fragmentAbsoluteRect.contains(absolutePoint))
return &fragment;
if (&fragment == endRegion)
break;
}
return nullptr;
}
void RenderNamedFlowThread::computeOverflow(LayoutUnit oldClientAfterEdge, bool recomputeFloats)
{
RenderFlowThread::computeOverflow(oldClientAfterEdge, recomputeFloats);
m_flowContentBottom = oldClientAfterEdge;
}
void RenderNamedFlowThread::layout()
{
RenderFlowThread::layout();
// If the number of regions has changed since we last computed the overset property, schedule the regionOversetChange event.
if (previousRegionCountChanged()) {
setDispatchRegionOversetChangeEvent(true);
updatePreviousRegionCount();
}
}
void RenderNamedFlowThread::dispatchNamedFlowEvents()
{
ASSERT(inFinalLayoutPhase());
dispatchRegionOversetChangeEventIfNeeded();
}
void RenderNamedFlowThread::checkInvalidRegions()
{
Vector<RenderNamedFlowFragment*> newValidFragments;
for (auto& region : m_invalidRegionList) {
auto& namedFlowFragment = downcast<RenderNamedFlowFragment>(*region);
// The only reason a region would be invalid is because it has a parent flow thread.
ASSERT(!namedFlowFragment.isValid() && namedFlowFragment.parentNamedFlowThread());
if (namedFlowFragment.parentNamedFlowThread()->dependsOn(this))
continue;
newValidFragments.append(&namedFlowFragment);
}
for (auto& namedFlowFragment : newValidFragments) {
m_invalidRegionList.remove(namedFlowFragment);
namedFlowFragment->parentNamedFlowThread()->m_observerThreadsSet.remove(this);
addFragmentToNamedFlowThread(namedFlowFragment);
}
if (!newValidFragments.isEmpty())
invalidateRegions();
if (m_observerThreadsSet.isEmpty())
return;
// Notify all the flow threads that were dependent on this flow.
// Create a copy of the list first. That's because observers might change the list when calling checkInvalidRegions.
Vector<RenderNamedFlowThread*> observers;
copyToVector(m_observerThreadsSet, observers);
for (auto& flowThread : observers)
flowThread->checkInvalidRegions();
}
void RenderNamedFlowThread::addDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread)
{
RenderNamedFlowThreadCountedSet::AddResult result = m_layoutBeforeThreadsSet.add(otherFlowThread);
if (result.isNewEntry) {
// This is the first time we see this dependency. Make sure we recalculate all the dependencies.
view().flowThreadController().setIsRenderNamedFlowThreadOrderDirty(true);
}
}
void RenderNamedFlowThread::removeDependencyOnFlowThread(RenderNamedFlowThread* otherFlowThread)
{
bool removed = m_layoutBeforeThreadsSet.remove(otherFlowThread);
if (removed) {
checkInvalidRegions();
view().flowThreadController().setIsRenderNamedFlowThreadOrderDirty(true);
}
}
void RenderNamedFlowThread::pushDependencies(RenderNamedFlowThreadList& list)
{
for (auto& flowThreadPair : m_layoutBeforeThreadsSet) {
auto& flowThread = flowThreadPair.key;
if (list.contains(flowThread))
continue;
flowThread->pushDependencies(list);
list.add(flowThread);
}
}
// The content nodes list contains those nodes with -webkit-flow-into: flow.
// An element with display:none should also be listed among those nodes.
// The list of nodes is ordered.
void RenderNamedFlowThread::registerNamedFlowContentElement(Element& contentElement)
{
ASSERT(&contentElement.document() == &document());
contentElement.setIsNamedFlowContentElement();
resetMarkForDestruction();
// Find the first content node following the new content node.
for (auto& element : m_contentElements) {
unsigned short position = contentElement.compareDocumentPosition(*element);
if (position & Node::DOCUMENT_POSITION_FOLLOWING) {
m_contentElements.insertBefore(element, &contentElement);
InspectorInstrumentation::didRegisterNamedFlowContentElement(document(), namedFlow(), contentElement, element);
return;
}
}
m_contentElements.add(&contentElement);
InspectorInstrumentation::didRegisterNamedFlowContentElement(document(), namedFlow(), contentElement);
}
void RenderNamedFlowThread::unregisterNamedFlowContentElement(Element& contentElement)
{
ASSERT(m_contentElements.contains(&contentElement));
ASSERT(contentElement.isNamedFlowContentElement());
ASSERT(&contentElement.document() == &document());
contentElement.clearIsNamedFlowContentElement();
m_contentElements.remove(&contentElement);
if (canBeDestroyed())
setMarkForDestruction();
InspectorInstrumentation::didUnregisterNamedFlowContentElement(document(), namedFlow(), contentElement);
}
bool RenderNamedFlowThread::hasContentElement(Element& contentElement) const
{
return m_contentElements.contains(&contentElement);
}
const AtomicString& RenderNamedFlowThread::flowThreadName() const
{
return namedFlow().name();
}
bool RenderNamedFlowThread::isChildAllowed(const RenderObject& child, const RenderStyle& style) const
{
if (!child.node())
return true;
ASSERT(is<Element>(*child.node()));
auto* originalParent = composedTreeAncestors(*child.node()).first();
if (!originalParent || !originalParent->renderer())
return true;
return originalParent->renderer()->isChildAllowed(child, style);
}
void RenderNamedFlowThread::dispatchRegionOversetChangeEventIfNeeded()
{
if (!m_dispatchRegionOversetChangeEvent)
return;
m_dispatchRegionOversetChangeEvent = false;
InspectorInstrumentation::didChangeRegionOverset(document(), namedFlow());
if (!m_regionOversetChangeEventTimer.isActive() && namedFlow().hasEventListeners())
m_regionOversetChangeEventTimer.startOneShot(0);
}
void RenderNamedFlowThread::regionOversetChangeEventTimerFired()
{
namedFlow().dispatchRegionOversetChangeEvent();
}
void RenderNamedFlowThread::setMarkForDestruction()
{
if (namedFlow().flowState() == WebKitNamedFlow::FlowStateNull)
return;
namedFlow().setRenderer(nullptr);
// After this call ends, the renderer can be safely destroyed.
// The NamedFlow object may outlive its renderer if it's referenced from a script and may be reatached to one if the named flow is recreated in the stylesheet.
}
void RenderNamedFlowThread::resetMarkForDestruction()
{
if (namedFlow().flowState() == WebKitNamedFlow::FlowStateCreated)
return;
namedFlow().setRenderer(this);
}
bool RenderNamedFlowThread::isMarkedForDestruction() const
{
// Flow threads in the "NULL" state can be destroyed.
return namedFlow().flowState() == WebKitNamedFlow::FlowStateNull;
}
static bool isContainedInElements(const Vector<Element*>& others, Element* element)
{
for (auto& other : others) {
if (other->contains(element))
return true;
}
return false;
}
static bool boxIntersectsRegion(LayoutUnit logicalTopForBox, LayoutUnit logicalBottomForBox, LayoutUnit logicalTopForRegion, LayoutUnit logicalBottomForRegion)
{
bool regionIsEmpty = logicalBottomForRegion != LayoutUnit::max() && logicalTopForRegion != LayoutUnit::min()
&& (logicalBottomForRegion - logicalTopForRegion) <= 0;
return (logicalBottomForBox - logicalTopForBox) > 0
&& !regionIsEmpty
&& logicalTopForBox < logicalBottomForRegion && logicalTopForRegion < logicalBottomForBox;
}
// Retrieve the next node to be visited while computing the ranges inside a region.
static Node* nextNodeInsideContentElement(const Node& currNode, const Element* contentElement)
{
ASSERT(contentElement && contentElement->isNamedFlowContentElement());
if (currNode.renderer() && currNode.renderer()->isSVGRoot())
return NodeTraversal::nextSkippingChildren(currNode, contentElement);
return NodeTraversal::next(currNode, contentElement);
}
void RenderNamedFlowThread::getRanges(Vector<RefPtr<Range>>& rangeObjects, const RenderNamedFlowFragment* namedFlowFragment) const
{
LayoutUnit logicalTopForRegion;
LayoutUnit logicalBottomForRegion;
// extend the first region top to contain everything up to its logical height
if (namedFlowFragment->isFirstRegion())
logicalTopForRegion = LayoutUnit::min();
else
logicalTopForRegion = namedFlowFragment->logicalTopForFlowThreadContent();
// extend the last region to contain everything above its y()
if (namedFlowFragment->isLastRegion())
logicalBottomForRegion = LayoutUnit::max();
else
logicalBottomForRegion = namedFlowFragment->logicalBottomForFlowThreadContent();
Vector<Element*> elements;
// eliminate the contentElements that are descendants of other contentElements
for (auto& element : contentElements()) {
if (!isContainedInElements(elements, element))
elements.append(element);
}
for (auto& contentElement : elements) {
if (!contentElement->renderer())
continue;
RefPtr<Range> range = Range::create(contentElement->document());
bool foundStartPosition = false;
bool startsAboveRegion = true;
bool endsBelowRegion = true;
bool skipOverOutsideNodes = false;
Node* lastEndNode = nullptr;
for (Node* node = contentElement; node; node = nextNodeInsideContentElement(*node, contentElement)) {
RenderObject* renderer = node->renderer();
if (!renderer)
continue;
LayoutRect boundingBox;
if (is<RenderInline>(*renderer))
boundingBox = downcast<RenderInline>(*renderer).linesBoundingBox();
else if (is<RenderText>(*renderer))
boundingBox = downcast<RenderText>(*renderer).linesBoundingBox();
else if (is<RenderLineBreak>(*renderer))
boundingBox = downcast<RenderLineBreak>(*renderer).linesBoundingBox();
else if (is<RenderBox>(*renderer)) {
auto& renderBox = downcast<RenderBox>(*renderer);
boundingBox = renderBox.frameRect();
if (renderBox.isRelPositioned())
boundingBox.move(renderBox.relativePositionLogicalOffset());
} else
continue;
LayoutUnit offsetTop = renderer->containingBlock()->offsetFromLogicalTopOfFirstPage();
const LayoutPoint logicalOffsetFromTop(isHorizontalWritingMode() ? LayoutUnit() : offsetTop,
isHorizontalWritingMode() ? offsetTop : LayoutUnit());
boundingBox.moveBy(logicalOffsetFromTop);
LayoutUnit logicalTopForRenderer = namedFlowFragment->logicalTopOfFlowThreadContentRect(boundingBox);
LayoutUnit logicalBottomForRenderer = namedFlowFragment->logicalBottomOfFlowThreadContentRect(boundingBox);
// if the bounding box of the current element doesn't intersect the region box
// close the current range only if the start element began inside the region,
// otherwise just move the start position after this node and keep skipping them until we found a proper start position.
if (!boxIntersectsRegion(logicalTopForRenderer, logicalBottomForRenderer, logicalTopForRegion, logicalBottomForRegion)) {
if (foundStartPosition) {
if (!startsAboveRegion) {
auto intersectsResult = range->intersectsNode(*node);
if (!intersectsResult.hasException() && intersectsResult.releaseReturnValue())
range->setEndBefore(*node);
rangeObjects.append(range->cloneRange());
range = Range::create(contentElement->document());
startsAboveRegion = true;
} else
skipOverOutsideNodes = true;
}
if (skipOverOutsideNodes)
range->setStartAfter(*node);
foundStartPosition = false;
continue;
}
// start position
if (logicalTopForRenderer < logicalTopForRegion && startsAboveRegion) {
if (is<RenderText>(*renderer)) {
// Text crosses region top
// for Text elements, just find the last textbox that is contained inside the region and use its start() offset as start position
RenderText& textRenderer = downcast<RenderText>(*renderer);
for (InlineTextBox* box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) {
if (offsetTop + box->logicalBottom() < logicalTopForRegion)
continue;
range->setStart(Position(downcast<Text>(node), box->start()));
startsAboveRegion = false;
break;
}
} else {
// node crosses region top
// for all elements, except Text, just set the start position to be before their children
startsAboveRegion = true;
range->setStart(Position(node, Position::PositionIsBeforeChildren));
}
} else {
// node starts inside region
// for elements that start inside the region, set the start position to be before them. If we found one, we will just skip the others until
// the range is closed.
if (startsAboveRegion) {
startsAboveRegion = false;
range->setStartBefore(*node);
}
}
skipOverOutsideNodes = false;
foundStartPosition = true;
// end position
if (logicalBottomForRegion < logicalBottomForRenderer && (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode)))) {
// for Text elements, just find just find the last textbox that is contained inside the region and use its start()+len() offset as end position
if (is<RenderText>(*renderer)) {
// Text crosses region bottom
RenderText& textRenderer = downcast<RenderText>(*renderer);
InlineTextBox* lastBox = nullptr;
for (InlineTextBox* box = textRenderer.firstTextBox(); box; box = box->nextTextBox()) {
if ((offsetTop + box->logicalTop()) < logicalBottomForRegion) {
lastBox = box;
continue;
}
ASSERT(lastBox);
if (lastBox)
range->setEnd(Position(downcast<Text>(node), lastBox->start() + lastBox->len()));
break;
}
endsBelowRegion = false;
lastEndNode = node;
} else {
// node crosses region bottom
// for all elements, except Text, just set the start position to be after their children
range->setEnd(Position(node, Position::PositionIsAfterChildren));
endsBelowRegion = true;
lastEndNode = node;
}
} else {
// node ends inside region
// for elements that ends inside the region, set the end position to be after them
// allow this end position to be changed only by other elements that are not descendants of the current end node
if (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode))) {
range->setEndAfter(*node);
endsBelowRegion = false;
lastEndNode = node;
}
}
}
if (foundStartPosition || skipOverOutsideNodes)
rangeObjects.append(range);
}
}
void RenderNamedFlowThread::applyBreakAfterContent(LayoutUnit clientHeight)
{
// Simulate a region break at height. If it points inside an auto logical height region,
// then it may determine the region computed autoheight.
addForcedRegionBreak(this, clientHeight, this, false);
}
bool RenderNamedFlowThread::collectsGraphicsLayersUnderRegions() const
{
// We only need to map layers to regions for named flow threads.
// Multi-column threads are displayed on top of the regions and do not require
// distributing the layers.
return true;
}
// Check if the content is flown into at least a region with region styling rules.
void RenderNamedFlowThread::checkRegionsWithStyling()
{
bool hasRegionsWithStyling = false;
for (const auto& region : m_regionList) {
if (downcast<RenderNamedFlowFragment>(*region).hasCustomRegionStyle()) {
hasRegionsWithStyling = true;
break;
}
}
m_hasRegionsWithStyling = hasRegionsWithStyling;
}
void RenderNamedFlowThread::clearRenderObjectCustomStyle(const RenderElement& object)
{
// Clear the styles for the object in the regions.
// FIXME: Region styling is not computed only for the region range of the object so this is why we need to walk the whole chain.
for (auto& region : m_regionList)
downcast<RenderNamedFlowFragment>(*region).clearObjectStyleInRegion(object);
}
void RenderNamedFlowThread::removeFlowChildInfo(RenderElement& child)
{
RenderFlowThread::removeFlowChildInfo(child);
clearRenderObjectCustomStyle(child);
}
bool RenderNamedFlowThread::absoluteQuadsForBox(Vector<FloatQuad>& quads, bool* wasFixed, const RenderBox* renderer, float localTop, float localBottom) const
{
RenderRegion* startRegion = nullptr;
RenderRegion* endRegion = nullptr;
// If the box doesn't have a range, we don't know how it is fragmented so fallback to the default behaviour.
if (!computedRegionRangeForBox(renderer, startRegion, endRegion))
return false;
for (auto iter = m_regionList.find(startRegion), end = m_regionList.end(); iter != end; ++iter) {
RenderRegion* region = *iter;
region->absoluteQuadsForBoxInRegion(quads, wasFixed, renderer, localTop, localBottom);
if (region == endRegion)
break;
}
return true;
}
}