| /* |
| * 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; |
| } |
| |
| } |