| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (C) 1999 Antti Koivisto (koivisto@kde.org) |
| * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. |
| * All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| * |
| */ |
| |
| #include "core/layout/LayoutInline.h" |
| |
| #include "core/dom/Fullscreen.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutBlock.h" |
| #include "core/layout/LayoutFullScreen.h" |
| #include "core/layout/LayoutGeometryMap.h" |
| #include "core/layout/LayoutTheme.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/api/LineLayoutBoxModel.h" |
| #include "core/layout/line/InlineTextBox.h" |
| #include "core/paint/BoxPainter.h" |
| #include "core/paint/InlinePainter.h" |
| #include "core/paint/ObjectPainter.h" |
| #include "core/paint/PaintLayer.h" |
| #include "core/style/StyleInheritedData.h" |
| #include "platform/geometry/FloatQuad.h" |
| #include "platform/geometry/Region.h" |
| #include "platform/geometry/TransformState.h" |
| |
| namespace blink { |
| |
| struct SameSizeAsLayoutInline : public LayoutBoxModelObject { |
| virtual ~SameSizeAsLayoutInline() {} |
| LayoutObjectChildList m_children; |
| LineBoxList m_lineBoxes; |
| }; |
| |
| static_assert(sizeof(LayoutInline) == sizeof(SameSizeAsLayoutInline), |
| "LayoutInline should stay small"); |
| |
| LayoutInline::LayoutInline(Element* element) : LayoutBoxModelObject(element) { |
| setChildrenInline(true); |
| } |
| |
| void LayoutInline::willBeDestroyed() { |
| // Make sure to destroy anonymous children first while they are still |
| // connected to the rest of the tree, so that they will properly dirty line |
| // boxes that they are removed from. Effects that do :before/:after only on |
| // hover could crash otherwise. |
| children()->destroyLeftoverChildren(); |
| |
| // Destroy our continuation before anything other than anonymous children. |
| // The reason we don't destroy it before anonymous children is that they may |
| // have continuations of their own that are anonymous children of our |
| // continuation. |
| LayoutBoxModelObject* continuation = this->continuation(); |
| if (continuation) { |
| continuation->destroy(); |
| setContinuation(nullptr); |
| } |
| |
| if (!documentBeingDestroyed()) { |
| if (firstLineBox()) { |
| // We can't wait for LayoutBoxModelObject::destroy to clear the selection, |
| // because by then we will have nuked the line boxes. |
| // FIXME: The FrameSelection should be responsible for this when it |
| // is notified of DOM mutations. |
| if (isSelectionBorder()) |
| view()->clearSelection(); |
| |
| // If line boxes are contained inside a root, that means we're an inline. |
| // In that case, we need to remove all the line boxes so that the parent |
| // lines aren't pointing to deleted children. If the first line box does |
| // not have a parent that means they are either already disconnected or |
| // root lines that can just be destroyed without disconnecting. |
| if (firstLineBox()->parent()) { |
| for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) |
| box->remove(); |
| } |
| } else if (parent()) { |
| parent()->dirtyLinesFromChangedChild(this); |
| } |
| } |
| |
| m_lineBoxes.deleteLineBoxes(); |
| |
| LayoutBoxModelObject::willBeDestroyed(); |
| } |
| |
| LayoutInline* LayoutInline::inlineElementContinuation() const { |
| LayoutBoxModelObject* continuation = this->continuation(); |
| if (!continuation || continuation->isInline()) |
| return toLayoutInline(continuation); |
| return toLayoutBlockFlow(continuation)->inlineElementContinuation(); |
| } |
| |
| void LayoutInline::updateFromStyle() { |
| LayoutBoxModelObject::updateFromStyle(); |
| |
| // FIXME: Is this still needed. Was needed for run-ins, since run-in is |
| // considered a block display type. |
| setInline(true); |
| |
| // FIXME: Support transforms and reflections on inline flows someday. |
| setHasTransformRelatedProperty(false); |
| setHasReflection(false); |
| } |
| |
| static LayoutObject* inFlowPositionedInlineAncestor(LayoutObject* p) { |
| while (p && p->isLayoutInline()) { |
| if (p->isInFlowPositioned()) |
| return p; |
| p = p->parent(); |
| } |
| return nullptr; |
| } |
| |
| static void updateInFlowPositionOfAnonymousBlockContinuations( |
| LayoutObject* block, |
| const ComputedStyle& newStyle, |
| const ComputedStyle& oldStyle, |
| LayoutObject* containingBlockOfEndOfContinuation) { |
| for (; block && block != containingBlockOfEndOfContinuation && |
| block->isAnonymousBlock(); |
| block = block->nextSibling()) { |
| LayoutBlockFlow* blockFlow = toLayoutBlockFlow(block); |
| if (!blockFlow->isAnonymousBlockContinuation()) |
| continue; |
| |
| // If we are no longer in-flow positioned but our descendant block(s) still |
| // have an in-flow positioned ancestor then their containing anonymous block |
| // should keep its in-flow positioning. |
| if (oldStyle.hasInFlowPosition() && |
| inFlowPositionedInlineAncestor(blockFlow->inlineElementContinuation())) |
| continue; |
| |
| RefPtr<ComputedStyle> newBlockStyle = |
| ComputedStyle::clone(block->styleRef()); |
| newBlockStyle->setPosition(newStyle.position()); |
| block->setStyle(newBlockStyle); |
| } |
| } |
| |
| void LayoutInline::styleDidChange(StyleDifference diff, |
| const ComputedStyle* oldStyle) { |
| LayoutBoxModelObject::styleDidChange(diff, oldStyle); |
| |
| // Ensure that all of the split inlines pick up the new style. We only do this |
| // if we're an inline, since we don't want to propagate a block's style to the |
| // other inlines. e.g., <font>foo <h4>goo</h4> moo</font>. The <font> inlines |
| // before and after the block share the same style, but the block doesn't need |
| // to pass its style on to anyone else. |
| const ComputedStyle& newStyle = styleRef(); |
| LayoutInline* continuation = inlineElementContinuation(); |
| LayoutInline* endOfContinuation = nullptr; |
| for (LayoutInline* currCont = continuation; currCont; |
| currCont = currCont->inlineElementContinuation()) { |
| LayoutBoxModelObject* nextCont = currCont->continuation(); |
| currCont->setContinuation(nullptr); |
| currCont->setStyle(mutableStyle()); |
| currCont->setContinuation(nextCont); |
| endOfContinuation = currCont; |
| } |
| |
| if (continuation && oldStyle) { |
| ASSERT(endOfContinuation); |
| LayoutObject* block = containingBlock()->nextSibling(); |
| // If an inline's in-flow positioning has changed then any descendant blocks |
| // will need to change their styles accordingly. |
| if (block && block->isAnonymousBlock() && |
| newStyle.position() != oldStyle->position() && |
| (newStyle.hasInFlowPosition() || oldStyle->hasInFlowPosition())) |
| updateInFlowPositionOfAnonymousBlockContinuations( |
| block, newStyle, *oldStyle, endOfContinuation->containingBlock()); |
| } |
| |
| if (!alwaysCreateLineBoxes()) { |
| bool alwaysCreateLineBoxesNew = |
| hasSelfPaintingLayer() || hasBoxDecorationBackground() || |
| newStyle.hasPadding() || newStyle.hasMargin() || newStyle.hasOutline(); |
| if (oldStyle && alwaysCreateLineBoxesNew) { |
| dirtyLineBoxes(false); |
| setNeedsLayoutAndFullPaintInvalidation( |
| LayoutInvalidationReason::StyleChange); |
| } |
| setAlwaysCreateLineBoxes(alwaysCreateLineBoxesNew); |
| } |
| |
| // If we are changing to/from static, we need to reposition |
| // out-of-flow positioned descendants. |
| if (oldStyle && oldStyle->position() != newStyle.position() && |
| (newStyle.position() == EPosition::kStatic || |
| oldStyle->position() == EPosition::kStatic)) { |
| LayoutBlock* absContainingBlock = nullptr; |
| if (oldStyle->position() == EPosition::kStatic) { |
| absContainingBlock = containingBlockForAbsolutePosition(); |
| } else { |
| // When position was not static, containingBlockForAbsolutePosition |
| // for our children is our existing containingBlock. |
| absContainingBlock = containingBlock(); |
| } |
| if (absContainingBlock) |
| absContainingBlock->removePositionedObjects(this, NewContainingBlock); |
| } |
| |
| propagateStyleToAnonymousChildren(); |
| } |
| |
| void LayoutInline::updateAlwaysCreateLineBoxes(bool fullLayout) { |
| // Once we have been tainted once, just assume it will happen again. This way |
| // effects like hover highlighting that change the background color will only |
| // cause a layout on the first rollover. |
| if (alwaysCreateLineBoxes()) |
| return; |
| |
| const ComputedStyle& parentStyle = parent()->styleRef(); |
| LayoutInline* parentLayoutInline = |
| parent()->isLayoutInline() ? toLayoutInline(parent()) : 0; |
| bool checkFonts = document().inNoQuirksMode(); |
| bool alwaysCreateLineBoxesNew = |
| (parentLayoutInline && parentLayoutInline->alwaysCreateLineBoxes()) || |
| (parentLayoutInline && |
| parentStyle.verticalAlign() != EVerticalAlign::kBaseline) || |
| style()->verticalAlign() != EVerticalAlign::kBaseline || |
| style()->getTextEmphasisMark() != TextEmphasisMarkNone || |
| (checkFonts && |
| (!styleRef().hasIdenticalAscentDescentAndLineGap(parentStyle) || |
| parentStyle.lineHeight() != styleRef().lineHeight())); |
| |
| if (!alwaysCreateLineBoxesNew && checkFonts && |
| document().styleEngine().usesFirstLineRules()) { |
| // Have to check the first line style as well. |
| const ComputedStyle& firstLineParentStyle = parent()->styleRef(true); |
| const ComputedStyle& childStyle = styleRef(true); |
| alwaysCreateLineBoxesNew = |
| !firstLineParentStyle.hasIdenticalAscentDescentAndLineGap(childStyle) || |
| childStyle.verticalAlign() != EVerticalAlign::kBaseline || |
| firstLineParentStyle.lineHeight() != childStyle.lineHeight(); |
| } |
| |
| if (alwaysCreateLineBoxesNew) { |
| if (!fullLayout) |
| dirtyLineBoxes(false); |
| setAlwaysCreateLineBoxes(); |
| } |
| } |
| |
| LayoutRect LayoutInline::localCaretRect(InlineBox* inlineBox, |
| int, |
| LayoutUnit* extraWidthToEndOfLine) { |
| if (firstChild()) { |
| // This condition is possible if the LayoutInline is at an editing boundary, |
| // i.e. the VisiblePosition is: |
| // <LayoutInline editingBoundary=true>|<LayoutText> |
| // </LayoutText></LayoutInline> |
| // FIXME: need to figure out how to make this return a valid rect, note that |
| // there are no line boxes created in the above case. |
| return LayoutRect(); |
| } |
| |
| DCHECK(!inlineBox); |
| |
| if (extraWidthToEndOfLine) |
| *extraWidthToEndOfLine = LayoutUnit(); |
| |
| LayoutRect caretRect = |
| localCaretRectForEmptyElement(borderAndPaddingWidth(), LayoutUnit()); |
| |
| if (InlineBox* firstBox = firstLineBox()) |
| caretRect.moveBy(firstBox->location()); |
| |
| return caretRect; |
| } |
| |
| void LayoutInline::addChild(LayoutObject* newChild, LayoutObject* beforeChild) { |
| // Any table-part dom child of an inline element has anonymous wrappers in the |
| // layout tree so we need to climb up to the enclosing anonymous table wrapper |
| // and add the new child before that. |
| // TODO(rhogan): If newChild is a table part we want to insert it into the |
| // same table as beforeChild. |
| while (beforeChild && beforeChild->isTablePart()) |
| beforeChild = beforeChild->parent(); |
| if (continuation()) |
| return addChildToContinuation(newChild, beforeChild); |
| return addChildIgnoringContinuation(newChild, beforeChild); |
| } |
| |
| static LayoutBoxModelObject* nextContinuation(LayoutObject* layoutObject) { |
| if (layoutObject->isInline() && !layoutObject->isAtomicInlineLevel()) |
| return toLayoutInline(layoutObject)->continuation(); |
| return toLayoutBlockFlow(layoutObject)->inlineElementContinuation(); |
| } |
| |
| LayoutBoxModelObject* LayoutInline::continuationBefore( |
| LayoutObject* beforeChild) { |
| if (beforeChild && beforeChild->parent() == this) |
| return this; |
| |
| LayoutBoxModelObject* curr = nextContinuation(this); |
| LayoutBoxModelObject* nextToLast = this; |
| LayoutBoxModelObject* last = this; |
| while (curr) { |
| if (beforeChild && beforeChild->parent() == curr) { |
| if (curr->slowFirstChild() == beforeChild) |
| return last; |
| return curr; |
| } |
| |
| nextToLast = last; |
| last = curr; |
| curr = nextContinuation(curr); |
| } |
| |
| if (!beforeChild && !last->slowFirstChild()) |
| return nextToLast; |
| return last; |
| } |
| |
| void LayoutInline::addChildIgnoringContinuation(LayoutObject* newChild, |
| LayoutObject* beforeChild) { |
| // Make sure we don't append things after :after-generated content if we have |
| // it. |
| if (!beforeChild && isAfterContent(lastChild())) |
| beforeChild = lastChild(); |
| |
| if (!newChild->isInline() && !newChild->isFloatingOrOutOfFlowPositioned() && |
| !newChild->isTablePart()) { |
| // We are placing a block inside an inline. We have to perform a split of |
| // this inline into continuations. This involves creating an anonymous |
| // block box to hold |newChild|. We then make that block box a continuation |
| // of this inline. We take all of the children after |beforeChild| and put |
| // them in a clone of this object. |
| RefPtr<ComputedStyle> newStyle = |
| ComputedStyle::createAnonymousStyleWithDisplay( |
| containingBlock()->styleRef(), EDisplay::Block); |
| |
| // If inside an inline affected by in-flow positioning the block needs to be |
| // affected by it too. Giving the block a layer like this allows it to |
| // collect the x/y offsets from inline parents later. |
| if (LayoutObject* positionedAncestor = inFlowPositionedInlineAncestor(this)) |
| newStyle->setPosition(positionedAncestor->style()->position()); |
| |
| LayoutBlockFlow* newBox = LayoutBlockFlow::createAnonymous(&document()); |
| newBox->setStyle(std::move(newStyle)); |
| LayoutBoxModelObject* oldContinuation = continuation(); |
| setContinuation(newBox); |
| |
| splitFlow(beforeChild, newBox, newChild, oldContinuation); |
| return; |
| } |
| |
| LayoutBoxModelObject::addChild(newChild, beforeChild); |
| |
| newChild->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::ChildChanged); |
| } |
| |
| LayoutInline* LayoutInline::clone() const { |
| LayoutInline* cloneInline = new LayoutInline(node()); |
| cloneInline->setStyle(mutableStyle()); |
| cloneInline->setIsInsideFlowThread(isInsideFlowThread()); |
| return cloneInline; |
| } |
| |
| void LayoutInline::moveChildrenToIgnoringContinuation( |
| LayoutInline* to, |
| LayoutObject* startChild) { |
| LayoutObject* child = startChild; |
| while (child) { |
| LayoutObject* currentChild = child; |
| child = currentChild->nextSibling(); |
| to->addChildIgnoringContinuation( |
| children()->removeChildNode(this, currentChild), nullptr); |
| } |
| } |
| |
| void LayoutInline::splitInlines(LayoutBlockFlow* fromBlock, |
| LayoutBlockFlow* toBlock, |
| LayoutBlockFlow* middleBlock, |
| LayoutObject* beforeChild, |
| LayoutBoxModelObject* oldCont) { |
| ASSERT(isDescendantOf(fromBlock)); |
| |
| // If we're splitting the inline containing the fullscreened element, |
| // |beforeChild| may be the layoutObject for the fullscreened element. |
| // However, that layoutObject is wrapped in a LayoutFullScreen, so |this| is |
| // not its parent. Since the splitting logic expects |this| to be the parent, |
| // set |beforeChild| to be the LayoutFullScreen. |
| if (Fullscreen* fullscreen = Fullscreen::fromIfExists(document())) { |
| const Element* fullScreenElement = fullscreen->currentFullScreenElement(); |
| if (fullScreenElement && beforeChild && |
| beforeChild->node() == fullScreenElement) |
| beforeChild = fullscreen->fullScreenLayoutObject(); |
| } |
| |
| // FIXME: Because splitting is O(n^2) as tags nest pathologically, we cap the |
| // depth at which we're willing to clone. |
| // There will eventually be a better approach to this problem that will let us |
| // nest to a much greater depth (see bugzilla bug 13430) but for now we have a |
| // limit. This *will* result in incorrect rendering, but the alternative is to |
| // hang forever. |
| const unsigned cMaxSplitDepth = 200; |
| Vector<LayoutInline*> inlinesToClone; |
| LayoutInline* topMostInline = this; |
| for (LayoutObject* o = this; o != fromBlock; o = o->parent()) { |
| topMostInline = toLayoutInline(o); |
| if (inlinesToClone.size() < cMaxSplitDepth) |
| inlinesToClone.push_back(topMostInline); |
| // Keep walking up the chain to ensure |topMostInline| is a child of |
| // |fromBlock|, to avoid assertion failure when |fromBlock|'s children are |
| // moved to |toBlock| below. |
| } |
| |
| // Create a new clone of the top-most inline in |inlinesToClone|. |
| LayoutInline* topMostInlineToClone = inlinesToClone.back(); |
| LayoutInline* cloneInline = topMostInlineToClone->clone(); |
| |
| // Now we are at the block level. We need to put the clone into the |toBlock|. |
| toBlock->children()->appendChildNode(toBlock, cloneInline); |
| |
| // Now take all the children after |topMostInline| and remove them from the |
| // |fromBlock| and put them into the toBlock. |
| fromBlock->moveChildrenTo(toBlock, topMostInline->nextSibling(), nullptr, |
| true); |
| |
| LayoutInline* currentParent = topMostInlineToClone; |
| LayoutInline* cloneInlineParent = cloneInline; |
| |
| // Clone the inlines from top to down to ensure any new object will be added |
| // into a rooted tree. |
| // Note that we have already cloned the top-most one, so the loop begins from |
| // size - 2 (except if we have reached |cMaxDepth| in which case we sacrifice |
| // correct rendering for performance). |
| for (int i = static_cast<int>(inlinesToClone.size()) - 2; i >= 0; --i) { |
| // Hook the clone up as a continuation of |currentInline|. |
| LayoutBoxModelObject* oldCont = currentParent->continuation(); |
| currentParent->setContinuation(cloneInline); |
| cloneInline->setContinuation(oldCont); |
| |
| // Create a new clone. |
| LayoutInline* current = inlinesToClone[i]; |
| cloneInline = current->clone(); |
| |
| // Insert our |cloneInline| as the first child of |cloneInlineParent|. |
| cloneInlineParent->addChildIgnoringContinuation(cloneInline, nullptr); |
| |
| // Now we need to take all of the children starting from the first child |
| // *after* |current| and append them all to the |cloneInlineParent|. |
| currentParent->moveChildrenToIgnoringContinuation(cloneInlineParent, |
| current->nextSibling()); |
| |
| currentParent = current; |
| cloneInlineParent = cloneInline; |
| } |
| |
| // The last inline to clone is |this|, and the current |cloneInline| is cloned |
| // from |this|. |
| ASSERT(this == inlinesToClone.front()); |
| |
| // Hook |cloneInline| up as the continuation of the middle block. |
| cloneInline->setContinuation(oldCont); |
| middleBlock->setContinuation(cloneInline); |
| |
| // Now take all of the children from |beforeChild| to the end and remove |
| // them from |this| and place them in the clone. |
| moveChildrenToIgnoringContinuation(cloneInline, beforeChild); |
| } |
| |
| void LayoutInline::splitFlow(LayoutObject* beforeChild, |
| LayoutBlockFlow* newBlockBox, |
| LayoutObject* newChild, |
| LayoutBoxModelObject* oldCont) { |
| LayoutBlockFlow* block = toLayoutBlockFlow(containingBlock()); |
| LayoutBlockFlow* pre = nullptr; |
| |
| // Delete our line boxes before we do the inline split into continuations. |
| block->deleteLineBoxTree(); |
| |
| bool reusedAnonymousBlock = false; |
| if (block->isAnonymousBlock()) { |
| LayoutBlock* outerContainingBlock = block->containingBlock(); |
| if (outerContainingBlock && outerContainingBlock->isLayoutBlockFlow() && |
| !outerContainingBlock->createsAnonymousWrapper()) { |
| // We can reuse this block and make it the preBlock of the next |
| // continuation. |
| block->removePositionedObjects(nullptr); |
| block->removeFloatingObjects(); |
| pre = block; |
| block = toLayoutBlockFlow(outerContainingBlock); |
| reusedAnonymousBlock = true; |
| } |
| } |
| |
| // No anonymous block available for use. Make one. |
| if (!reusedAnonymousBlock) |
| pre = toLayoutBlockFlow(block->createAnonymousBlock()); |
| |
| LayoutBlockFlow* post = toLayoutBlockFlow(pre->createAnonymousBlock()); |
| |
| LayoutObject* boxFirst = |
| !reusedAnonymousBlock ? block->firstChild() : pre->nextSibling(); |
| if (!reusedAnonymousBlock) |
| block->children()->insertChildNode(block, pre, boxFirst); |
| block->children()->insertChildNode(block, newBlockBox, boxFirst); |
| block->children()->insertChildNode(block, post, boxFirst); |
| block->setChildrenInline(false); |
| |
| if (!reusedAnonymousBlock) { |
| LayoutObject* o = boxFirst; |
| while (o) { |
| LayoutObject* no = o; |
| o = no->nextSibling(); |
| pre->children()->appendChildNode( |
| pre, block->children()->removeChildNode(block, no)); |
| no->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::AnonymousBlockChange); |
| } |
| } |
| |
| splitInlines(pre, post, newBlockBox, beforeChild, oldCont); |
| |
| // We already know the newBlockBox isn't going to contain inline kids, so |
| // avoid wasting time in makeChildrenNonInline by just setting this explicitly |
| // up front. |
| newBlockBox->setChildrenInline(false); |
| |
| newBlockBox->addChild(newChild); |
| |
| // Always just do a full layout in order to ensure that line boxes (especially |
| // wrappers for images) get deleted properly. Because objects moves from the |
| // pre block into the post block, we want to make new line boxes instead of |
| // leaving the old line boxes around. |
| pre->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::AnonymousBlockChange); |
| block->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::AnonymousBlockChange); |
| post->setNeedsLayoutAndPrefWidthsRecalcAndFullPaintInvalidation( |
| LayoutInvalidationReason::AnonymousBlockChange); |
| } |
| |
| void LayoutInline::addChildToContinuation(LayoutObject* newChild, |
| LayoutObject* beforeChild) { |
| // A continuation always consists of two potential candidates: an inline or an |
| // anonymous block box holding block children. |
| LayoutBoxModelObject* flow = continuationBefore(beforeChild); |
| ASSERT(!beforeChild || beforeChild->parent()->isAnonymousBlock() || |
| beforeChild->parent()->isLayoutInline()); |
| LayoutBoxModelObject* beforeChildParent = nullptr; |
| if (beforeChild) { |
| beforeChildParent = toLayoutBoxModelObject(beforeChild->parent()); |
| } else { |
| LayoutBoxModelObject* cont = nextContinuation(flow); |
| if (cont) |
| beforeChildParent = cont; |
| else |
| beforeChildParent = flow; |
| } |
| |
| // If the two candidates are the same, no further checking is necessary. |
| if (flow == beforeChildParent) |
| return flow->addChildIgnoringContinuation(newChild, beforeChild); |
| |
| // A table part will be wrapped by an inline anonymous table when it is added |
| // to the layout tree, so treat it as inline when deciding where to add |
| // it. Floating and out-of-flow-positioned objects can also live under |
| // inlines, and since this about adding a child to an inline parent, we |
| // should not put them into a block continuation. |
| bool addInsideInline = newChild->isInline() || newChild->isTablePart() || |
| newChild->isFloatingOrOutOfFlowPositioned(); |
| |
| // The goal here is to match up if we can, so that we can coalesce and create |
| // the minimal # of continuations needed for the inline. |
| if (addInsideInline == beforeChildParent->isInline() || |
| (beforeChild && beforeChild->isInline())) { |
| return beforeChildParent->addChildIgnoringContinuation(newChild, |
| beforeChild); |
| } |
| if (addInsideInline == flow->isInline()) { |
| // Just treat like an append. |
| return flow->addChildIgnoringContinuation(newChild); |
| } |
| return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); |
| } |
| |
| void LayoutInline::paint(const PaintInfo& paintInfo, |
| const LayoutPoint& paintOffset) const { |
| InlinePainter(*this).paint(paintInfo, paintOffset); |
| } |
| |
| template <typename GeneratorContext> |
| void LayoutInline::generateLineBoxRects(GeneratorContext& yield) const { |
| if (!alwaysCreateLineBoxes()) { |
| generateCulledLineBoxRects(yield, this); |
| } else if (InlineFlowBox* curr = firstLineBox()) { |
| for (; curr; curr = curr->nextLineBox()) |
| yield(LayoutRect(curr->location(), curr->size())); |
| } |
| } |
| |
| static inline void computeItemTopHeight(const LayoutInline* container, |
| const RootInlineBox& rootBox, |
| LayoutUnit* top, |
| LayoutUnit* height) { |
| bool firstLine = rootBox.isFirstLineStyle(); |
| const SimpleFontData* fontData = |
| rootBox.getLineLayoutItem().style(firstLine)->font().primaryFont(); |
| const SimpleFontData* containerFontData = |
| container->style(firstLine)->font().primaryFont(); |
| DCHECK(fontData && containerFontData); |
| if (!fontData || !containerFontData) { |
| *top = LayoutUnit(); |
| *height = LayoutUnit(); |
| return; |
| } |
| auto metrics = fontData->getFontMetrics(); |
| auto containerMetrics = containerFontData->getFontMetrics(); |
| *top = rootBox.logicalTop() + (metrics.ascent() - containerMetrics.ascent()); |
| *height = LayoutUnit(containerMetrics.height()); |
| } |
| |
| template <typename GeneratorContext> |
| void LayoutInline::generateCulledLineBoxRects( |
| GeneratorContext& yield, |
| const LayoutInline* container) const { |
| if (!culledInlineFirstLineBox()) |
| return; |
| |
| bool isHorizontal = style()->isHorizontalWritingMode(); |
| |
| LayoutUnit logicalTop, logicalHeight; |
| for (LayoutObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (curr->isFloatingOrOutOfFlowPositioned()) |
| continue; |
| |
| // We want to get the margin box in the inline direction, and then use our |
| // font ascent/descent in the block direction (aligned to the root box's |
| // baseline). |
| if (curr->isBox()) { |
| LayoutBox* currBox = toLayoutBox(curr); |
| if (currBox->inlineBoxWrapper()) { |
| RootInlineBox& rootBox = currBox->inlineBoxWrapper()->root(); |
| computeItemTopHeight(container, rootBox, &logicalTop, &logicalHeight); |
| if (isHorizontal) { |
| yield(LayoutRect( |
| currBox->inlineBoxWrapper()->x() - currBox->marginLeft(), |
| logicalTop, currBox->size().width() + currBox->marginWidth(), |
| logicalHeight)); |
| } else { |
| yield(LayoutRect(logicalTop, currBox->inlineBoxWrapper()->y() - |
| currBox->marginTop(), |
| logicalHeight, |
| currBox->size().height() + currBox->marginHeight())); |
| } |
| } |
| } else if (curr->isLayoutInline()) { |
| // If the child doesn't need line boxes either, then we can recur. |
| LayoutInline* currInline = toLayoutInline(curr); |
| if (!currInline->alwaysCreateLineBoxes()) { |
| currInline->generateCulledLineBoxRects(yield, container); |
| } else { |
| for (InlineFlowBox* childLine = currInline->firstLineBox(); childLine; |
| childLine = childLine->nextLineBox()) { |
| RootInlineBox& rootBox = childLine->root(); |
| computeItemTopHeight(container, rootBox, &logicalTop, &logicalHeight); |
| LayoutUnit logicalWidth = |
| childLine->logicalWidth() + childLine->marginLogicalWidth(); |
| if (isHorizontal) { |
| yield(LayoutRect( |
| LayoutUnit(childLine->x() - childLine->marginLogicalLeft()), |
| logicalTop, logicalWidth, logicalHeight)); |
| } else { |
| yield(LayoutRect( |
| logicalTop, |
| LayoutUnit(childLine->y() - childLine->marginLogicalLeft()), |
| logicalHeight, logicalWidth)); |
| } |
| } |
| } |
| } else if (curr->isText()) { |
| LayoutText* currText = toLayoutText(curr); |
| for (InlineTextBox* childText = currText->firstTextBox(); childText; |
| childText = childText->nextTextBox()) { |
| RootInlineBox& rootBox = childText->root(); |
| computeItemTopHeight(container, rootBox, &logicalTop, &logicalHeight); |
| if (isHorizontal) |
| yield(LayoutRect(childText->x(), logicalTop, |
| childText->logicalWidth(), logicalHeight)); |
| else |
| yield(LayoutRect(logicalTop, childText->y(), logicalHeight, |
| childText->logicalWidth())); |
| } |
| } |
| } |
| } |
| |
| namespace { |
| |
| class AbsoluteRectsGeneratorContext { |
| public: |
| AbsoluteRectsGeneratorContext(Vector<IntRect>& rects, |
| const LayoutPoint& accumulatedOffset) |
| : m_rects(rects), m_accumulatedOffset(accumulatedOffset) {} |
| |
| void operator()(const LayoutRect& rect) { |
| IntRect intRect = enclosingIntRect(rect); |
| intRect.move(m_accumulatedOffset.x().toInt(), |
| m_accumulatedOffset.y().toInt()); |
| m_rects.push_back(intRect); |
| } |
| |
| private: |
| Vector<IntRect>& m_rects; |
| const LayoutPoint& m_accumulatedOffset; |
| }; |
| |
| } // unnamed namespace |
| |
| void LayoutInline::absoluteRects(Vector<IntRect>& rects, |
| const LayoutPoint& accumulatedOffset) const { |
| AbsoluteRectsGeneratorContext context(rects, accumulatedOffset); |
| generateLineBoxRects(context); |
| if (rects.isEmpty()) |
| context(LayoutRect()); |
| |
| if (const LayoutBoxModelObject* continuation = this->continuation()) { |
| if (continuation->isBox()) { |
| const LayoutBox* box = toLayoutBox(continuation); |
| continuation->absoluteRects( |
| rects, |
| toLayoutPoint(accumulatedOffset - containingBlock()->location() + |
| box->locationOffset())); |
| } else { |
| continuation->absoluteRects( |
| rects, |
| toLayoutPoint(accumulatedOffset - containingBlock()->location())); |
| } |
| } |
| } |
| |
| namespace { |
| |
| class AbsoluteQuadsGeneratorContext { |
| public: |
| AbsoluteQuadsGeneratorContext(const LayoutInline* layoutObject, |
| Vector<FloatQuad>& quads, |
| MapCoordinatesFlags mode) |
| : m_quads(quads), m_geometryMap(mode) { |
| m_geometryMap.pushMappingsToAncestor(layoutObject, 0); |
| } |
| |
| void operator()(const FloatRect& rect) { |
| m_quads.push_back(m_geometryMap.absoluteRect(rect)); |
| } |
| void operator()(const LayoutRect& rect) { operator()(FloatRect(rect)); } |
| |
| private: |
| Vector<FloatQuad>& m_quads; |
| LayoutGeometryMap m_geometryMap; |
| }; |
| |
| } // unnamed namespace |
| |
| void LayoutInline::absoluteQuadsForSelf(Vector<FloatQuad>& quads, |
| MapCoordinatesFlags mode) const { |
| AbsoluteQuadsGeneratorContext context(this, quads, mode); |
| generateLineBoxRects(context); |
| if (quads.isEmpty()) |
| context(FloatRect()); |
| } |
| |
| LayoutPoint LayoutInline::firstLineBoxTopLeft() const { |
| if (InlineBox* firstBox = firstLineBoxIncludingCulling()) |
| return firstBox->location(); |
| return LayoutPoint(); |
| } |
| |
| LayoutUnit LayoutInline::offsetLeft(const Element* parent) const { |
| return adjustedPositionRelativeTo(firstLineBoxTopLeft(), parent).x(); |
| } |
| |
| LayoutUnit LayoutInline::offsetTop(const Element* parent) const { |
| return adjustedPositionRelativeTo(firstLineBoxTopLeft(), parent).y(); |
| } |
| |
| static LayoutUnit computeMargin(const LayoutInline* layoutObject, |
| const Length& margin) { |
| if (margin.isFixed()) |
| return LayoutUnit(margin.value()); |
| if (margin.isPercentOrCalc()) |
| return minimumValueForLength( |
| margin, |
| std::max(LayoutUnit(), |
| layoutObject->containingBlock()->availableLogicalWidth())); |
| return LayoutUnit(); |
| } |
| |
| LayoutRectOutsets LayoutInline::marginBoxOutsets() const { |
| return LayoutRectOutsets(marginTop(), marginRight(), marginBottom(), |
| marginLeft()); |
| } |
| |
| LayoutUnit LayoutInline::marginLeft() const { |
| return computeMargin(this, style()->marginLeft()); |
| } |
| |
| LayoutUnit LayoutInline::marginRight() const { |
| return computeMargin(this, style()->marginRight()); |
| } |
| |
| LayoutUnit LayoutInline::marginTop() const { |
| return computeMargin(this, style()->marginTop()); |
| } |
| |
| LayoutUnit LayoutInline::marginBottom() const { |
| return computeMargin(this, style()->marginBottom()); |
| } |
| |
| LayoutUnit LayoutInline::marginStart(const ComputedStyle* otherStyle) const { |
| return computeMargin( |
| this, style()->marginStartUsing(otherStyle ? otherStyle : style())); |
| } |
| |
| LayoutUnit LayoutInline::marginEnd(const ComputedStyle* otherStyle) const { |
| return computeMargin( |
| this, style()->marginEndUsing(otherStyle ? otherStyle : style())); |
| } |
| |
| LayoutUnit LayoutInline::marginBefore(const ComputedStyle* otherStyle) const { |
| return computeMargin( |
| this, style()->marginBeforeUsing(otherStyle ? otherStyle : style())); |
| } |
| |
| LayoutUnit LayoutInline::marginAfter(const ComputedStyle* otherStyle) const { |
| return computeMargin( |
| this, style()->marginAfterUsing(otherStyle ? otherStyle : style())); |
| } |
| |
| LayoutUnit LayoutInline::marginOver() const { |
| return computeMargin(this, style()->marginOver()); |
| } |
| |
| LayoutUnit LayoutInline::marginUnder() const { |
| return computeMargin(this, style()->marginUnder()); |
| } |
| |
| bool LayoutInline::nodeAtPoint(HitTestResult& result, |
| const HitTestLocation& locationInContainer, |
| const LayoutPoint& accumulatedOffset, |
| HitTestAction hitTestAction) { |
| return m_lineBoxes.hitTest(LineLayoutBoxModel(this), result, |
| locationInContainer, accumulatedOffset, |
| hitTestAction); |
| } |
| |
| namespace { |
| |
| class HitTestCulledInlinesGeneratorContext { |
| public: |
| HitTestCulledInlinesGeneratorContext(Region& region, |
| const HitTestLocation& location) |
| : m_intersected(false), m_region(region), m_location(location) {} |
| void operator()(const FloatRect& rect) { |
| if (m_location.intersects(rect)) { |
| m_intersected = true; |
| m_region.unite(enclosingIntRect(rect)); |
| } |
| } |
| void operator()(const LayoutRect& rect) { |
| if (m_location.intersects(rect)) { |
| m_intersected = true; |
| m_region.unite(enclosingIntRect(rect)); |
| } |
| } |
| bool intersected() const { return m_intersected; } |
| |
| private: |
| bool m_intersected; |
| Region& m_region; |
| const HitTestLocation& m_location; |
| }; |
| |
| } // unnamed namespace |
| |
| bool LayoutInline::hitTestCulledInline( |
| HitTestResult& result, |
| const HitTestLocation& locationInContainer, |
| const LayoutPoint& accumulatedOffset) { |
| ASSERT(!alwaysCreateLineBoxes()); |
| if (!visibleToHitTestRequest(result.hitTestRequest())) |
| return false; |
| |
| HitTestLocation adjustedLocation(locationInContainer, |
| -toLayoutSize(accumulatedOffset)); |
| |
| Region regionResult; |
| HitTestCulledInlinesGeneratorContext context(regionResult, adjustedLocation); |
| generateCulledLineBoxRects(context, this); |
| |
| if (context.intersected()) { |
| updateHitTestResult(result, adjustedLocation.point()); |
| if (result.addNodeToListBasedTestResult(node(), adjustedLocation, |
| regionResult) == StopHitTesting) |
| return true; |
| } |
| return false; |
| } |
| |
| PositionWithAffinity LayoutInline::positionForPoint(const LayoutPoint& point) { |
| // FIXME: Does not deal with relative positioned inlines (should it?) |
| |
| // If there are continuations, test them first because our containing block |
| // will not check them. |
| LayoutBoxModelObject* continuation = this->continuation(); |
| while (continuation) { |
| if (continuation->isInline() || continuation->slowFirstChild()) |
| return continuation->positionForPoint(point); |
| continuation = toLayoutBlockFlow(continuation)->inlineElementContinuation(); |
| } |
| |
| if (firstLineBoxIncludingCulling()) { |
| // This inline actually has a line box. We must have clicked in the |
| // border/padding of one of these boxes. We |
| // should try to find a result by asking our containing block. |
| return containingBlock()->positionForPoint(point); |
| } |
| |
| return LayoutBoxModelObject::positionForPoint(point); |
| } |
| |
| namespace { |
| |
| class LinesBoundingBoxGeneratorContext { |
| public: |
| LinesBoundingBoxGeneratorContext(FloatRect& rect) : m_rect(rect) {} |
| void operator()(const FloatRect& rect) { m_rect.uniteIfNonZero(rect); } |
| void operator()(const LayoutRect& rect) { operator()(FloatRect(rect)); } |
| |
| private: |
| FloatRect& m_rect; |
| }; |
| |
| } // unnamed namespace |
| |
| LayoutRect LayoutInline::linesBoundingBox() const { |
| if (!alwaysCreateLineBoxes()) { |
| ASSERT(!firstLineBox()); |
| FloatRect floatResult; |
| LinesBoundingBoxGeneratorContext context(floatResult); |
| generateCulledLineBoxRects(context, this); |
| return enclosingLayoutRect(floatResult); |
| } |
| |
| LayoutRect result; |
| |
| // See <rdar://problem/5289721>, for an unknown reason the linked list here is |
| // sometimes inconsistent, first is non-zero and last is zero. We have been |
| // unable to reproduce this at all (and consequently unable to figure ot why |
| // this is happening). The assert will hopefully catch the problem in debug |
| // builds and help us someday figure out why. We also put in a redundant |
| // check of lastLineBox() to avoid the crash for now. |
| ASSERT(!firstLineBox() == |
| !lastLineBox()); // Either both are null or both exist. |
| if (firstLineBox() && lastLineBox()) { |
| // Return the width of the minimal left side and the maximal right side. |
| LayoutUnit logicalLeftSide; |
| LayoutUnit logicalRightSide; |
| for (InlineFlowBox* curr = firstLineBox(); curr; |
| curr = curr->nextLineBox()) { |
| if (curr == firstLineBox() || curr->logicalLeft() < logicalLeftSide) |
| logicalLeftSide = curr->logicalLeft(); |
| if (curr == firstLineBox() || curr->logicalRight() > logicalRightSide) |
| logicalRightSide = curr->logicalRight(); |
| } |
| |
| bool isHorizontal = style()->isHorizontalWritingMode(); |
| |
| LayoutUnit x = isHorizontal ? logicalLeftSide : firstLineBox()->x(); |
| LayoutUnit y = isHorizontal ? firstLineBox()->y() : logicalLeftSide; |
| LayoutUnit width = isHorizontal ? logicalRightSide - logicalLeftSide |
| : lastLineBox()->logicalBottom() - x; |
| LayoutUnit height = isHorizontal ? lastLineBox()->logicalBottom() - y |
| : logicalRightSide - logicalLeftSide; |
| result = LayoutRect(x, y, width, height); |
| } |
| |
| return result; |
| } |
| |
| InlineBox* LayoutInline::culledInlineFirstLineBox() const { |
| for (LayoutObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (curr->isFloatingOrOutOfFlowPositioned()) |
| continue; |
| |
| // We want to get the margin box in the inline direction, and then use our |
| // font ascent/descent in the block direction (aligned to the root box's |
| // baseline). |
| if (curr->isBox()) |
| return toLayoutBox(curr)->inlineBoxWrapper(); |
| if (curr->isLayoutInline()) { |
| LayoutInline* currInline = toLayoutInline(curr); |
| InlineBox* result = currInline->firstLineBoxIncludingCulling(); |
| if (result) |
| return result; |
| } else if (curr->isText()) { |
| LayoutText* currText = toLayoutText(curr); |
| if (currText->firstTextBox()) |
| return currText->firstTextBox(); |
| } |
| } |
| return nullptr; |
| } |
| |
| InlineBox* LayoutInline::culledInlineLastLineBox() const { |
| for (LayoutObject* curr = lastChild(); curr; curr = curr->previousSibling()) { |
| if (curr->isFloatingOrOutOfFlowPositioned()) |
| continue; |
| |
| // We want to get the margin box in the inline direction, and then use our |
| // font ascent/descent in the block direction (aligned to the root box's |
| // baseline). |
| if (curr->isBox()) |
| return toLayoutBox(curr)->inlineBoxWrapper(); |
| if (curr->isLayoutInline()) { |
| LayoutInline* currInline = toLayoutInline(curr); |
| InlineBox* result = currInline->lastLineBoxIncludingCulling(); |
| if (result) |
| return result; |
| } else if (curr->isText()) { |
| LayoutText* currText = toLayoutText(curr); |
| if (currText->lastTextBox()) |
| return currText->lastTextBox(); |
| } |
| } |
| return nullptr; |
| } |
| |
| LayoutRect LayoutInline::culledInlineVisualOverflowBoundingBox() const { |
| FloatRect floatResult; |
| LinesBoundingBoxGeneratorContext context(floatResult); |
| generateCulledLineBoxRects(context, this); |
| LayoutRect result(enclosingLayoutRect(floatResult)); |
| bool isHorizontal = style()->isHorizontalWritingMode(); |
| for (LayoutObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (curr->isFloatingOrOutOfFlowPositioned()) |
| continue; |
| |
| // For overflow we just have to propagate by hand and recompute it all. |
| if (curr->isBox()) { |
| LayoutBox* currBox = toLayoutBox(curr); |
| if (!currBox->hasSelfPaintingLayer() && currBox->inlineBoxWrapper()) { |
| LayoutRect logicalRect = |
| currBox->logicalVisualOverflowRectForPropagation(styleRef()); |
| if (isHorizontal) { |
| logicalRect.moveBy(currBox->location()); |
| result.uniteIfNonZero(logicalRect); |
| } else { |
| logicalRect.moveBy(currBox->location()); |
| result.uniteIfNonZero(logicalRect.transposedRect()); |
| } |
| } |
| } else if (curr->isLayoutInline()) { |
| // If the child doesn't need line boxes either, then we can recur. |
| LayoutInline* currInline = toLayoutInline(curr); |
| if (!currInline->alwaysCreateLineBoxes()) |
| result.uniteIfNonZero( |
| currInline->culledInlineVisualOverflowBoundingBox()); |
| else if (!currInline->hasSelfPaintingLayer()) |
| result.uniteIfNonZero(currInline->visualOverflowRect()); |
| } else if (curr->isText()) { |
| LayoutText* currText = toLayoutText(curr); |
| result.uniteIfNonZero(currText->visualOverflowRect()); |
| } |
| } |
| return result; |
| } |
| |
| LayoutRect LayoutInline::linesVisualOverflowBoundingBox() const { |
| if (!alwaysCreateLineBoxes()) |
| return culledInlineVisualOverflowBoundingBox(); |
| |
| if (!firstLineBox() || !lastLineBox()) |
| return LayoutRect(); |
| |
| // Return the width of the minimal left side and the maximal right side. |
| LayoutUnit logicalLeftSide = LayoutUnit::max(); |
| LayoutUnit logicalRightSide = LayoutUnit::min(); |
| for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { |
| logicalLeftSide = |
| std::min(logicalLeftSide, curr->logicalLeftVisualOverflow()); |
| logicalRightSide = |
| std::max(logicalRightSide, curr->logicalRightVisualOverflow()); |
| } |
| |
| RootInlineBox& firstRootBox = firstLineBox()->root(); |
| RootInlineBox& lastRootBox = lastLineBox()->root(); |
| |
| LayoutUnit logicalTop = |
| firstLineBox()->logicalTopVisualOverflow(firstRootBox.lineTop()); |
| LayoutUnit logicalWidth = logicalRightSide - logicalLeftSide; |
| LayoutUnit logicalHeight = |
| lastLineBox()->logicalBottomVisualOverflow(lastRootBox.lineBottom()) - |
| logicalTop; |
| |
| LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight); |
| if (!style()->isHorizontalWritingMode()) |
| rect = rect.transposedRect(); |
| return rect; |
| } |
| |
| LayoutRect LayoutInline::absoluteVisualRect() const { |
| if (!continuation()) { |
| LayoutRect rect = visualOverflowRect(); |
| mapToVisualRectInAncestorSpace(view(), rect); |
| return rect; |
| } |
| |
| FloatRect floatResult; |
| LinesBoundingBoxGeneratorContext context(floatResult); |
| |
| LayoutInline* endContinuation = inlineElementContinuation(); |
| while (LayoutInline* nextContinuation = |
| endContinuation->inlineElementContinuation()) |
| endContinuation = nextContinuation; |
| |
| for (LayoutBlock* currBlock = containingBlock(); |
| currBlock && currBlock->isAnonymousBlock(); |
| currBlock = toLayoutBlock(currBlock->nextSibling())) { |
| bool walkChildrenOnly = !currBlock->childrenInline(); |
| for (LayoutObject* curr = currBlock->firstChild(); curr; |
| curr = curr->nextSibling()) { |
| LayoutRect rect(curr->localVisualRect()); |
| context(FloatRect(rect)); |
| if (walkChildrenOnly) |
| continue; |
| for (LayoutObject* walker = curr; walker; |
| walker = walker->nextInPreOrder(curr)) { |
| if (walker != endContinuation) |
| continue; |
| LayoutRect rect(enclosingIntRect(floatResult)); |
| mapToVisualRectInAncestorSpace(view(), rect); |
| return rect; |
| } |
| } |
| } |
| return LayoutRect(); |
| } |
| |
| LayoutRect LayoutInline::localVisualRect() const { |
| // If we don't create line boxes, we don't have any invalidations to do. |
| if (!alwaysCreateLineBoxes()) |
| return LayoutRect(); |
| |
| if (style()->visibility() != EVisibility::kVisible) |
| return LayoutRect(); |
| |
| return visualOverflowRect(); |
| } |
| |
| LayoutRect LayoutInline::visualOverflowRect() const { |
| LayoutRect overflowRect = linesVisualOverflowBoundingBox(); |
| LayoutUnit outlineOutset(style()->outlineOutsetExtent()); |
| if (outlineOutset) { |
| Vector<LayoutRect> rects; |
| if (document().inNoQuirksMode()) { |
| // We have already included outline extents of line boxes in |
| // linesVisualOverflowBoundingBox(), so the following just add outline |
| // rects for children and continuations. |
| addOutlineRectsForChildrenAndContinuations( |
| rects, LayoutPoint(), outlineRectsShouldIncludeBlockVisualOverflow()); |
| } else { |
| // In non-standard mode, because the difference in |
| // LayoutBlock::minLineHeightForReplacedObject(), |
| // linesVisualOverflowBoundingBox() may not cover outline rects of lines |
| // containing replaced objects. |
| addOutlineRects(rects, LayoutPoint(), |
| outlineRectsShouldIncludeBlockVisualOverflow()); |
| } |
| if (!rects.isEmpty()) { |
| LayoutRect outlineRect = unionRectEvenIfEmpty(rects); |
| outlineRect.inflate(outlineOutset); |
| overflowRect.unite(outlineRect); |
| } |
| } |
| return overflowRect; |
| } |
| |
| bool LayoutInline::mapToVisualRectInAncestorSpaceInternal( |
| const LayoutBoxModelObject* ancestor, |
| TransformState& transformState, |
| VisualRectFlags visualRectFlags) const { |
| if (ancestor == this) |
| return true; |
| |
| LayoutObject* container = this->container(); |
| ASSERT(container == parent()); |
| if (!container) |
| return true; |
| |
| bool preserve3D = container->style()->preserves3D() || style()->preserves3D(); |
| |
| TransformState::TransformAccumulation accumulation = |
| preserve3D ? TransformState::AccumulateTransform |
| : TransformState::FlattenTransform; |
| |
| if (style()->hasInFlowPosition() && layer()) { |
| // Apply the in-flow position offset when invalidating a rectangle. The |
| // layer is translated, but the layout box isn't, so we need to do this to |
| // get the right dirty rect. Since this is called from LayoutObject:: |
| // setStyle, the relative position flag on the LayoutObject has been |
| // cleared, so use the one on the style(). |
| transformState.move(layer()->offsetForInFlowPosition(), accumulation); |
| } |
| |
| LayoutBox* containerBox = |
| container->isBox() ? toLayoutBox(container) : nullptr; |
| if (containerBox && container != ancestor && |
| !containerBox->mapScrollingContentsRectToBoxSpace( |
| transformState, accumulation, visualRectFlags)) |
| return false; |
| |
| // TODO(wkorman): Generalize Ruby specialization and/or document more clearly. |
| if (containerBox && !isRuby()) { |
| transformState.flatten(); |
| LayoutRect rect(transformState.lastPlanarQuad().boundingBox()); |
| containerBox->flipForWritingMode(rect); |
| transformState.setQuad(FloatQuad(FloatRect(rect))); |
| } |
| return container->mapToVisualRectInAncestorSpaceInternal( |
| ancestor, transformState, visualRectFlags); |
| } |
| |
| LayoutSize LayoutInline::offsetFromContainer( |
| const LayoutObject* container) const { |
| ASSERT(container == this->container()); |
| |
| LayoutSize offset; |
| if (isInFlowPositioned()) |
| offset += offsetForInFlowPosition(); |
| |
| if (container->hasOverflowClip()) |
| offset -= toLayoutBox(container)->scrolledContentOffset(); |
| |
| return offset; |
| } |
| |
| PaintLayerType LayoutInline::layerTypeRequired() const { |
| return isInFlowPositioned() || createsGroup() || hasClipPath() || |
| style()->shouldCompositeForCurrentAnimations() || |
| style()->hasCompositorProxy() || style()->containsPaint() |
| ? NormalPaintLayer |
| : NoPaintLayer; |
| } |
| |
| void LayoutInline::childBecameNonInline(LayoutObject* child) { |
| // We have to split the parent flow. |
| LayoutBlockFlow* newBox = |
| toLayoutBlockFlow(containingBlock()->createAnonymousBlock()); |
| LayoutBoxModelObject* oldContinuation = continuation(); |
| setContinuation(newBox); |
| LayoutObject* beforeChild = child->nextSibling(); |
| children()->removeChildNode(this, child); |
| splitFlow(beforeChild, newBox, child, oldContinuation); |
| } |
| |
| void LayoutInline::updateHitTestResult(HitTestResult& result, |
| const LayoutPoint& point) { |
| if (result.innerNode()) |
| return; |
| |
| Node* n = node(); |
| LayoutPoint localPoint(point); |
| if (n) { |
| if (isInlineElementContinuation()) { |
| // We're in the continuation of a split inline. Adjust our local point to |
| // be in the coordinate space of the principal layoutObject's containing |
| // block. This will end up being the innerNode. |
| LayoutBlock* firstBlock = n->layoutObject()->containingBlock(); |
| |
| // Get our containing block. |
| LayoutBox* block = containingBlock(); |
| localPoint.moveBy(block->location() - firstBlock->locationOffset()); |
| } |
| |
| result.setNodeAndPosition(n, localPoint); |
| } |
| } |
| |
| void LayoutInline::dirtyLineBoxes(bool fullLayout) { |
| if (fullLayout) { |
| m_lineBoxes.deleteLineBoxes(); |
| return; |
| } |
| |
| if (!alwaysCreateLineBoxes()) { |
| // We have to grovel into our children in order to dirty the appropriate |
| // lines. |
| for (LayoutObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (curr->isFloatingOrOutOfFlowPositioned()) |
| continue; |
| if (curr->isBox() && !curr->needsLayout()) { |
| LayoutBox* currBox = toLayoutBox(curr); |
| if (currBox->inlineBoxWrapper()) |
| currBox->inlineBoxWrapper()->root().markDirty(); |
| } else if (!curr->selfNeedsLayout()) { |
| if (curr->isLayoutInline()) { |
| LayoutInline* currInline = toLayoutInline(curr); |
| for (InlineFlowBox* childLine = currInline->firstLineBox(); childLine; |
| childLine = childLine->nextLineBox()) |
| childLine->root().markDirty(); |
| } else if (curr->isText()) { |
| LayoutText* currText = toLayoutText(curr); |
| for (InlineTextBox* childText = currText->firstTextBox(); childText; |
| childText = childText->nextTextBox()) |
| childText->root().markDirty(); |
| } |
| } |
| } |
| } else { |
| m_lineBoxes.dirtyLineBoxes(); |
| } |
| } |
| |
| InlineFlowBox* LayoutInline::createInlineFlowBox() { |
| return new InlineFlowBox(LineLayoutItem(this)); |
| } |
| |
| InlineFlowBox* LayoutInline::createAndAppendInlineFlowBox() { |
| setAlwaysCreateLineBoxes(); |
| InlineFlowBox* flowBox = createInlineFlowBox(); |
| m_lineBoxes.appendLineBox(flowBox); |
| return flowBox; |
| } |
| |
| LayoutUnit LayoutInline::lineHeight( |
| bool firstLine, |
| LineDirectionMode /*direction*/, |
| LinePositionMode /*linePositionMode*/) const { |
| if (firstLine && document().styleEngine().usesFirstLineRules()) { |
| const ComputedStyle* s = style(firstLine); |
| if (s != style()) |
| return LayoutUnit(s->computedLineHeight()); |
| } |
| |
| return LayoutUnit(style()->computedLineHeight()); |
| } |
| |
| int LayoutInline::baselinePosition(FontBaseline baselineType, |
| bool firstLine, |
| LineDirectionMode direction, |
| LinePositionMode linePositionMode) const { |
| ASSERT(linePositionMode == PositionOnContainingLine); |
| const SimpleFontData* fontData = style(firstLine)->font().primaryFont(); |
| DCHECK(fontData); |
| if (!fontData) |
| return -1; |
| const FontMetrics& fontMetrics = fontData->getFontMetrics(); |
| return (fontMetrics.ascent(baselineType) + |
| (lineHeight(firstLine, direction, linePositionMode) - |
| fontMetrics.height()) / |
| 2) |
| .toInt(); |
| } |
| |
| LayoutSize LayoutInline::offsetForInFlowPositionedInline( |
| const LayoutBox& child) const { |
| // FIXME: This function isn't right with mixed writing modes. |
| |
| ASSERT(isInFlowPositioned()); |
| if (!isInFlowPositioned()) |
| return LayoutSize(); |
| |
| // When we have an enclosing relpositioned inline, we need to add in the |
| // offset of the first line box from the rest of the content, but only in the |
| // cases where we know we're positioned relative to the inline itself. |
| |
| LayoutSize logicalOffset; |
| LayoutUnit inlinePosition; |
| LayoutUnit blockPosition; |
| if (firstLineBox()) { |
| inlinePosition = firstLineBox()->logicalLeft(); |
| blockPosition = firstLineBox()->logicalTop(); |
| } else { |
| inlinePosition = layer()->staticInlinePosition(); |
| blockPosition = layer()->staticBlockPosition(); |
| } |
| |
| // Per http://www.w3.org/TR/CSS2/visudet.html#abs-non-replaced-width an |
| // absolute positioned box with a static position should locate itself as |
| // though it is a normal flow box in relation to its containing block. If this |
| // relative-positioned inline has a negative offset we need to compensate for |
| // it so that we align the positioned object with the edge of its containing |
| // block. |
| if (child.style()->hasStaticInlinePosition( |
| style()->isHorizontalWritingMode())) |
| logicalOffset.setWidth( |
| std::max(LayoutUnit(), -offsetForInFlowPosition().width())); |
| else |
| logicalOffset.setWidth(inlinePosition); |
| |
| if (!child.style()->hasStaticBlockPosition( |
| style()->isHorizontalWritingMode())) |
| logicalOffset.setHeight(blockPosition); |
| |
| return style()->isHorizontalWritingMode() ? logicalOffset |
| : logicalOffset.transposedSize(); |
| } |
| |
| void LayoutInline::imageChanged(WrappedImagePtr, const IntRect*) { |
| if (!parent()) |
| return; |
| |
| // FIXME: We can do better. |
| setShouldDoFullPaintInvalidation(); |
| } |
| |
| namespace { |
| |
| class AbsoluteLayoutRectsGeneratorContext { |
| public: |
| AbsoluteLayoutRectsGeneratorContext(Vector<LayoutRect>& rects, |
| const LayoutPoint& accumulatedOffset) |
| : m_rects(rects), m_accumulatedOffset(accumulatedOffset) {} |
| |
| void operator()(const FloatRect& rect) { operator()(LayoutRect(rect)); } |
| void operator()(const LayoutRect& rect) { |
| LayoutRect layoutRect(rect); |
| layoutRect.moveBy(m_accumulatedOffset); |
| m_rects.push_back(layoutRect); |
| } |
| |
| private: |
| Vector<LayoutRect>& m_rects; |
| const LayoutPoint& m_accumulatedOffset; |
| }; |
| |
| } // unnamed namespace |
| |
| void LayoutInline::addOutlineRects( |
| Vector<LayoutRect>& rects, |
| const LayoutPoint& additionalOffset, |
| IncludeBlockVisualOverflowOrNot includeBlockOverflows) const { |
| AbsoluteLayoutRectsGeneratorContext context(rects, additionalOffset); |
| generateLineBoxRects(context); |
| addOutlineRectsForChildrenAndContinuations(rects, additionalOffset, |
| includeBlockOverflows); |
| } |
| |
| void LayoutInline::addOutlineRectsForChildrenAndContinuations( |
| Vector<LayoutRect>& rects, |
| const LayoutPoint& additionalOffset, |
| IncludeBlockVisualOverflowOrNot includeBlockOverflows) const { |
| addOutlineRectsForNormalChildren(rects, additionalOffset, |
| includeBlockOverflows); |
| addOutlineRectsForContinuations(rects, additionalOffset, |
| includeBlockOverflows); |
| } |
| |
| void LayoutInline::addOutlineRectsForContinuations( |
| Vector<LayoutRect>& rects, |
| const LayoutPoint& additionalOffset, |
| IncludeBlockVisualOverflowOrNot includeBlockOverflows) const { |
| if (LayoutBoxModelObject* continuation = this->continuation()) { |
| if (continuation->isInline()) |
| continuation->addOutlineRects( |
| rects, |
| additionalOffset + (continuation->containingBlock()->location() - |
| containingBlock()->location()), |
| includeBlockOverflows); |
| else |
| continuation->addOutlineRects( |
| rects, additionalOffset + (toLayoutBox(continuation)->location() - |
| containingBlock()->location()), |
| includeBlockOverflows); |
| } |
| } |
| |
| FloatRect LayoutInline::localBoundingBoxRectForAccessibility() const { |
| Vector<LayoutRect> rects; |
| addOutlineRects(rects, LayoutPoint(), IncludeBlockVisualOverflow); |
| return FloatRect(unionRect(rects)); |
| } |
| |
| void LayoutInline::computeSelfHitTestRects( |
| Vector<LayoutRect>& rects, |
| const LayoutPoint& layerOffset) const { |
| AbsoluteLayoutRectsGeneratorContext context(rects, layerOffset); |
| generateLineBoxRects(context); |
| } |
| |
| void LayoutInline::addAnnotatedRegions(Vector<AnnotatedRegionValue>& regions) { |
| // Convert the style regions to absolute coordinates. |
| if (style()->visibility() != EVisibility::kVisible) |
| return; |
| |
| if (style()->getDraggableRegionMode() == DraggableRegionNone) |
| return; |
| |
| AnnotatedRegionValue region; |
| region.draggable = style()->getDraggableRegionMode() == DraggableRegionDrag; |
| region.bounds = LayoutRect(linesBoundingBox()); |
| |
| LayoutObject* container = containingBlock(); |
| if (!container) |
| container = this; |
| |
| FloatPoint absPos = container->localToAbsolute(); |
| region.bounds.setX(LayoutUnit(absPos.x() + region.bounds.x())); |
| region.bounds.setY(LayoutUnit(absPos.y() + region.bounds.y())); |
| |
| regions.push_back(region); |
| } |
| |
| void LayoutInline::invalidateDisplayItemClients( |
| PaintInvalidationReason invalidationReason) const { |
| ObjectPaintInvalidator paintInvalidator(*this); |
| paintInvalidator.invalidateDisplayItemClient(*this, invalidationReason); |
| |
| for (InlineFlowBox* box = firstLineBox(); box; box = box->nextLineBox()) |
| paintInvalidator.invalidateDisplayItemClient(*box, invalidationReason); |
| } |
| |
| // TODO(lunalu): Not to just dump 0, 0 as the x and y here |
| LayoutRect LayoutInline::debugRect() const { |
| IntRect linesBox = enclosingIntRect(linesBoundingBox()); |
| return LayoutRect(IntRect(0, 0, linesBox.width(), linesBox.height())); |
| } |
| |
| } // namespace blink |