| /* |
| * 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 "config.h" |
| #include "RenderInline.h" |
| |
| #include "Chrome.h" |
| #include "FloatQuad.h" |
| #include "GraphicsContext.h" |
| #include "HitTestResult.h" |
| #include "Page.h" |
| #include "RenderArena.h" |
| #include "RenderBlock.h" |
| #include "RenderLayer.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "TransformState.h" |
| #include "VisiblePosition.h" |
| |
| #if ENABLE(DASHBOARD_SUPPORT) |
| #include "Frame.h" |
| #endif |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| RenderInline::RenderInline(Node* node) |
| : RenderBoxModelObject(node) |
| , m_lineHeight(-1) |
| { |
| setChildrenInline(true); |
| } |
| |
| void RenderInline::destroy() |
| { |
| #ifndef NDEBUG |
| // Make sure we do not retain "this" in the continuation outline table map of our containing blocks. |
| if (parent() && style()->visibility() == VISIBLE && hasOutline()) { |
| bool containingBlockPaintsContinuationOutline = continuation() || isInlineElementContinuation(); |
| if (containingBlockPaintsContinuationOutline) { |
| if (RenderBlock* cb = containingBlock()) { |
| if (RenderBlock* cbCb = cb->containingBlock()) |
| ASSERT(!cbCb->paintsContinuationOutline(this)); |
| } |
| } |
| } |
| #endif |
| |
| // 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. |
| RenderBoxModelObject* continuation = this->continuation(); |
| if (continuation) { |
| continuation->destroy(); |
| setContinuation(0); |
| } |
| |
| if (!documentBeingDestroyed()) { |
| if (firstLineBox()) { |
| // We can't wait for RenderBoxModelObject::destroy to clear the selection, |
| // because by then we will have nuked the line boxes. |
| // FIXME: The SelectionController 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 (isInline() && parent()) |
| parent()->dirtyLinesFromChangedChild(this); |
| } |
| |
| m_lineBoxes.deleteLineBoxes(renderArena()); |
| |
| RenderBoxModelObject::destroy(); |
| } |
| |
| RenderInline* RenderInline::inlineElementContinuation() const |
| { |
| RenderBoxModelObject* continuation = this->continuation(); |
| if (!continuation || continuation->isInline()) |
| return toRenderInline(continuation); |
| return toRenderBlock(continuation)->inlineElementContinuation(); |
| } |
| |
| void RenderInline::updateBoxModelInfoFromStyle() |
| { |
| RenderBoxModelObject::updateBoxModelInfoFromStyle(); |
| |
| setInline(true); // Needed for run-ins, since run-in is considered a block display type. |
| |
| // FIXME: Support transforms and reflections on inline flows someday. |
| setHasTransform(false); |
| setHasReflection(false); |
| } |
| |
| void RenderInline::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| RenderBoxModelObject::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. |
| for (RenderInline* currCont = inlineElementContinuation(); currCont; currCont = currCont->inlineElementContinuation()) { |
| RenderBoxModelObject* nextCont = currCont->continuation(); |
| currCont->setContinuation(0); |
| currCont->setStyle(style()); |
| currCont->setContinuation(nextCont); |
| } |
| |
| m_lineHeight = -1; |
| |
| // Update pseudos for :before and :after now. |
| if (!isAnonymous() && document()->usesBeforeAfterRules()) { |
| children()->updateBeforeAfterContent(this, BEFORE); |
| children()->updateBeforeAfterContent(this, AFTER); |
| } |
| } |
| |
| void RenderInline::addChild(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| if (continuation()) |
| return addChildToContinuation(newChild, beforeChild); |
| return addChildIgnoringContinuation(newChild, beforeChild); |
| } |
| |
| static RenderBoxModelObject* nextContinuation(RenderObject* renderer) |
| { |
| if (renderer->isInline() && !renderer->isReplaced()) |
| return toRenderInline(renderer)->continuation(); |
| return toRenderBlock(renderer)->inlineElementContinuation(); |
| } |
| |
| RenderBoxModelObject* RenderInline::continuationBefore(RenderObject* beforeChild) |
| { |
| if (beforeChild && beforeChild->parent() == this) |
| return this; |
| |
| RenderBoxModelObject* curr = nextContinuation(this); |
| RenderBoxModelObject* nextToLast = this; |
| RenderBoxModelObject* last = this; |
| while (curr) { |
| if (beforeChild && beforeChild->parent() == curr) { |
| if (curr->firstChild() == beforeChild) |
| return last; |
| return curr; |
| } |
| |
| nextToLast = last; |
| last = curr; |
| curr = nextContinuation(curr); |
| } |
| |
| if (!beforeChild && !last->firstChild()) |
| return nextToLast; |
| return last; |
| } |
| |
| void RenderInline::addChildIgnoringContinuation(RenderObject* newChild, RenderObject* 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->isFloatingOrPositioned()) { |
| // 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<RenderStyle> newStyle = RenderStyle::createAnonymousStyle(style()); |
| newStyle->setDisplay(BLOCK); |
| |
| RenderBlock* newBox = new (renderArena()) RenderBlock(document() /* anonymous box */); |
| newBox->setStyle(newStyle.release()); |
| RenderBoxModelObject* oldContinuation = continuation(); |
| setContinuation(newBox); |
| |
| // Someone may have put a <p> inside a <q>, causing a split. When this happens, the :after content |
| // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that our :after |
| // content gets properly destroyed. |
| bool isLastChild = (beforeChild == lastChild()); |
| if (document()->usesBeforeAfterRules()) |
| children()->updateBeforeAfterContent(this, AFTER); |
| if (isLastChild && beforeChild != lastChild()) |
| beforeChild = 0; // We destroyed the last child, so now we need to update our insertion |
| // point to be 0. It's just a straight append now. |
| |
| splitFlow(beforeChild, newBox, newChild, oldContinuation); |
| return; |
| } |
| |
| RenderBoxModelObject::addChild(newChild, beforeChild); |
| |
| newChild->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| |
| RenderInline* RenderInline::cloneInline(RenderInline* src) |
| { |
| RenderInline* o = new (src->renderArena()) RenderInline(src->node()); |
| o->setStyle(src->style()); |
| return o; |
| } |
| |
| void RenderInline::splitInlines(RenderBlock* fromBlock, RenderBlock* toBlock, |
| RenderBlock* middleBlock, |
| RenderObject* beforeChild, RenderBoxModelObject* oldCont) |
| { |
| // Create a clone of this inline. |
| RenderInline* clone = cloneInline(this); |
| clone->setContinuation(oldCont); |
| |
| // Now take all of the children from beforeChild to the end and remove |
| // them from |this| and place them in the clone. |
| RenderObject* o = beforeChild; |
| while (o) { |
| RenderObject* tmp = o; |
| o = tmp->nextSibling(); |
| clone->addChildIgnoringContinuation(children()->removeChildNode(this, tmp), 0); |
| tmp->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| |
| // Hook |clone| up as the continuation of the middle block. |
| middleBlock->setContinuation(clone); |
| |
| // We have been reparented and are now under the fromBlock. We need |
| // to walk up our inline parent chain until we hit the containing block. |
| // Once we hit the containing block we're done. |
| RenderBoxModelObject* curr = toRenderBoxModelObject(parent()); |
| RenderBoxModelObject* currChild = this; |
| |
| // 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. |
| unsigned splitDepth = 1; |
| const unsigned cMaxSplitDepth = 200; |
| while (curr && curr != fromBlock) { |
| ASSERT(curr->isRenderInline()); |
| if (splitDepth < cMaxSplitDepth) { |
| // Create a new clone. |
| RenderInline* cloneChild = clone; |
| clone = cloneInline(toRenderInline(curr)); |
| |
| // Insert our child clone as the first child. |
| clone->addChildIgnoringContinuation(cloneChild, 0); |
| |
| // Hook the clone up as a continuation of |curr|. |
| RenderInline* inlineCurr = toRenderInline(curr); |
| oldCont = inlineCurr->continuation(); |
| inlineCurr->setContinuation(clone); |
| clone->setContinuation(oldCont); |
| |
| // Someone may have indirectly caused a <q> to split. When this happens, the :after content |
| // has to move into the inline continuation. Call updateBeforeAfterContent to ensure that the inline's :after |
| // content gets properly destroyed. |
| if (document()->usesBeforeAfterRules()) |
| inlineCurr->children()->updateBeforeAfterContent(inlineCurr, AFTER); |
| |
| // Now we need to take all of the children starting from the first child |
| // *after* currChild and append them all to the clone. |
| o = currChild->nextSibling(); |
| while (o) { |
| RenderObject* tmp = o; |
| o = tmp->nextSibling(); |
| clone->addChildIgnoringContinuation(inlineCurr->children()->removeChildNode(curr, tmp), 0); |
| tmp->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| } |
| |
| // Keep walking up the chain. |
| currChild = curr; |
| curr = toRenderBoxModelObject(curr->parent()); |
| splitDepth++; |
| } |
| |
| // Now we are at the block level. We need to put the clone into the toBlock. |
| toBlock->children()->appendChildNode(toBlock, clone); |
| |
| // Now take all the children after currChild and remove them from the fromBlock |
| // and put them in the toBlock. |
| o = currChild->nextSibling(); |
| while (o) { |
| RenderObject* tmp = o; |
| o = tmp->nextSibling(); |
| toBlock->children()->appendChildNode(toBlock, fromBlock->children()->removeChildNode(fromBlock, tmp)); |
| } |
| } |
| |
| void RenderInline::splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, |
| RenderObject* newChild, RenderBoxModelObject* oldCont) |
| { |
| RenderBlock* pre = 0; |
| RenderBlock* block = containingBlock(); |
| |
| // Delete our line boxes before we do the inline split into continuations. |
| block->deleteLineBoxTree(); |
| |
| bool madeNewBeforeBlock = false; |
| if (block->isAnonymousBlock() && (!block->parent() || !block->parent()->createsAnonymousWrapper())) { |
| // We can reuse this block and make it the preBlock of the next continuation. |
| pre = block; |
| pre->removePositionedObjects(0); |
| block = block->containingBlock(); |
| } else { |
| // No anonymous block available for use. Make one. |
| pre = block->createAnonymousBlock(); |
| madeNewBeforeBlock = true; |
| } |
| |
| RenderBlock* post = block->createAnonymousBlock(); |
| |
| RenderObject* boxFirst = madeNewBeforeBlock ? block->firstChild() : pre->nextSibling(); |
| if (madeNewBeforeBlock) |
| block->children()->insertChildNode(block, pre, boxFirst); |
| block->children()->insertChildNode(block, newBlockBox, boxFirst); |
| block->children()->insertChildNode(block, post, boxFirst); |
| block->setChildrenInline(false); |
| |
| if (madeNewBeforeBlock) { |
| RenderObject* o = boxFirst; |
| while (o) { |
| RenderObject* no = o; |
| o = no->nextSibling(); |
| pre->children()->appendChildNode(pre, block->children()->removeChildNode(block, no)); |
| no->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| } |
| |
| 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); |
| |
| // We delayed adding the newChild until now so that the |newBlockBox| would be fully |
| // connected, thus allowing newChild access to a renderArena should it need |
| // to wrap itself in additional boxes (e.g., table construction). |
| 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->setNeedsLayoutAndPrefWidthsRecalc(); |
| block->setNeedsLayoutAndPrefWidthsRecalc(); |
| post->setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| |
| void RenderInline::addChildToContinuation(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| RenderBoxModelObject* flow = continuationBefore(beforeChild); |
| ASSERT(!beforeChild || beforeChild->parent()->isRenderBlock() || beforeChild->parent()->isRenderInline()); |
| RenderBoxModelObject* beforeChildParent = 0; |
| if (beforeChild) |
| beforeChildParent = toRenderBoxModelObject(beforeChild->parent()); |
| else { |
| RenderBoxModelObject* cont = nextContinuation(flow); |
| if (cont) |
| beforeChildParent = cont; |
| else |
| beforeChildParent = flow; |
| } |
| |
| if (newChild->isFloatingOrPositioned()) |
| return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); |
| |
| // A continuation always consists of two potential candidates: an inline or an anonymous |
| // block box holding block children. |
| bool childInline = newChild->isInline(); |
| bool bcpInline = beforeChildParent->isInline(); |
| bool flowInline = flow->isInline(); |
| |
| if (flow == beforeChildParent) |
| return flow->addChildIgnoringContinuation(newChild, beforeChild); |
| else { |
| // 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 (childInline == bcpInline) |
| return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); |
| else if (flowInline == childInline) |
| return flow->addChildIgnoringContinuation(newChild, 0); // Just treat like an append. |
| else |
| return beforeChildParent->addChildIgnoringContinuation(newChild, beforeChild); |
| } |
| } |
| |
| void RenderInline::paint(PaintInfo& paintInfo, int tx, int ty) |
| { |
| m_lineBoxes.paint(this, paintInfo, tx, ty); |
| } |
| |
| void RenderInline::absoluteRects(Vector<IntRect>& rects, int tx, int ty) |
| { |
| if (InlineFlowBox* curr = firstLineBox()) { |
| for (; curr; curr = curr->nextLineBox()) |
| rects.append(IntRect(tx + curr->x(), ty + curr->y(), curr->logicalWidth(), curr->logicalHeight())); |
| } else |
| rects.append(IntRect(tx, ty, 0, 0)); |
| |
| if (continuation()) { |
| if (continuation()->isBox()) { |
| RenderBox* box = toRenderBox(continuation()); |
| continuation()->absoluteRects(rects, |
| tx - containingBlock()->x() + box->x(), |
| ty - containingBlock()->y() + box->y()); |
| } else |
| continuation()->absoluteRects(rects, tx - containingBlock()->x(), ty - containingBlock()->y()); |
| } |
| } |
| |
| void RenderInline::absoluteQuads(Vector<FloatQuad>& quads) |
| { |
| if (InlineFlowBox* curr = firstLineBox()) { |
| for (; curr; curr = curr->nextLineBox()) { |
| FloatRect localRect(curr->x(), curr->y(), curr->logicalWidth(), curr->logicalHeight()); |
| quads.append(localToAbsoluteQuad(localRect)); |
| } |
| } else |
| quads.append(localToAbsoluteQuad(FloatRect())); |
| |
| if (continuation()) |
| continuation()->absoluteQuads(quads); |
| } |
| |
| int RenderInline::offsetLeft() const |
| { |
| int x = RenderBoxModelObject::offsetLeft(); |
| if (firstLineBox()) |
| x += firstLineBox()->x(); |
| return x; |
| } |
| |
| int RenderInline::offsetTop() const |
| { |
| int y = RenderBoxModelObject::offsetTop(); |
| if (firstLineBox()) |
| y += firstLineBox()->y(); |
| return y; |
| } |
| |
| static int computeMargin(const RenderInline* renderer, const Length& margin) |
| { |
| if (margin.isAuto()) |
| return 0; |
| if (margin.isFixed()) |
| return margin.value(); |
| if (margin.isPercent()) |
| return margin.calcMinValue(max(0, renderer->containingBlock()->availableLogicalWidth())); |
| return 0; |
| } |
| |
| int RenderInline::marginLeft() const |
| { |
| return computeMargin(this, style()->marginLeft()); |
| } |
| |
| int RenderInline::marginRight() const |
| { |
| return computeMargin(this, style()->marginRight()); |
| } |
| |
| int RenderInline::marginTop() const |
| { |
| return computeMargin(this, style()->marginTop()); |
| } |
| |
| int RenderInline::marginBottom() const |
| { |
| return computeMargin(this, style()->marginBottom()); |
| } |
| |
| int RenderInline::marginStart() const |
| { |
| return computeMargin(this, style()->marginStart()); |
| } |
| |
| int RenderInline::marginEnd() const |
| { |
| return computeMargin(this, style()->marginEnd()); |
| } |
| |
| int RenderInline::marginBefore() const |
| { |
| return computeMargin(this, style()->marginBefore()); |
| } |
| |
| int RenderInline::marginAfter() const |
| { |
| return computeMargin(this, style()->marginAfter()); |
| } |
| |
| const char* RenderInline::renderName() const |
| { |
| if (isRelPositioned()) |
| return "RenderInline (relative positioned)"; |
| if (isAnonymous()) |
| return "RenderInline (generated)"; |
| if (isRunIn()) |
| return "RenderInline (run-in)"; |
| return "RenderInline"; |
| } |
| |
| bool RenderInline::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, |
| int x, int y, int tx, int ty, HitTestAction hitTestAction) |
| { |
| return m_lineBoxes.hitTest(this, request, result, x, y, tx, ty, hitTestAction); |
| } |
| |
| VisiblePosition RenderInline::positionForPoint(const IntPoint& point) |
| { |
| // FIXME: Does not deal with relative positioned inlines (should it?) |
| RenderBlock* cb = containingBlock(); |
| if (firstLineBox()) { |
| // 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 cb->positionForPoint(point); |
| } |
| |
| // Translate the coords from the pre-anonymous block to the post-anonymous block. |
| int parentBlockX = cb->x() + point.x(); |
| int parentBlockY = cb->y() + point.y(); |
| RenderBoxModelObject* c = continuation(); |
| while (c) { |
| RenderBox* contBlock = c->isInline() ? c->containingBlock() : toRenderBlock(c); |
| if (c->isInline() || c->firstChild()) |
| return c->positionForCoordinates(parentBlockX - contBlock->x(), parentBlockY - contBlock->y()); |
| c = toRenderBlock(c)->inlineElementContinuation(); |
| } |
| |
| return RenderBoxModelObject::positionForPoint(point); |
| } |
| |
| IntRect RenderInline::linesBoundingBox() const |
| { |
| IntRect 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. |
| float logicalLeftSide = 0; |
| float logicalRightSide = 0; |
| 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(); |
| |
| float x = isHorizontal ? logicalLeftSide : firstLineBox()->x(); |
| float y = isHorizontal ? firstLineBox()->y() : logicalLeftSide; |
| float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastLineBox()->logicalBottom() - x; |
| float height = isHorizontal ? lastLineBox()->logicalBottom() - y : logicalRightSide - logicalLeftSide; |
| result = enclosingIntRect(FloatRect(x, y, width, height)); |
| } |
| |
| return result; |
| } |
| |
| IntRect RenderInline::linesVisualOverflowBoundingBox() const |
| { |
| if (!firstLineBox() || !lastLineBox()) |
| return IntRect(); |
| |
| // Return the width of the minimal left side and the maximal right side. |
| float logicalLeftSide = numeric_limits<int>::max(); |
| float logicalRightSide = numeric_limits<int>::min(); |
| for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { |
| logicalLeftSide = min(logicalLeftSide, static_cast<float>(curr->logicalLeftVisualOverflow())); |
| logicalRightSide = max(logicalRightSide, static_cast<float>(curr->logicalRightVisualOverflow())); |
| } |
| |
| bool isHorizontal = style()->isHorizontalWritingMode(); |
| |
| float x = isHorizontal ? logicalLeftSide : firstLineBox()->minXVisualOverflow(); |
| float y = isHorizontal ? firstLineBox()->minYVisualOverflow() : logicalLeftSide; |
| float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastLineBox()->maxXVisualOverflow() - firstLineBox()->minXVisualOverflow(); |
| float height = isHorizontal ? lastLineBox()->maxYVisualOverflow() - firstLineBox()->minYVisualOverflow() : logicalRightSide - logicalLeftSide; |
| return enclosingIntRect(FloatRect(x, y, width, height)); |
| } |
| |
| IntRect RenderInline::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) |
| { |
| // Only run-ins are allowed in here during layout. |
| ASSERT(!view() || !view()->layoutStateEnabled() || isRunIn()); |
| |
| if (!firstLineBox() && !continuation()) |
| return IntRect(); |
| |
| // Find our leftmost position. |
| IntRect boundingBox(linesVisualOverflowBoundingBox()); |
| int left = boundingBox.x(); |
| int top = boundingBox.y(); |
| |
| // Now invalidate a rectangle. |
| int ow = style() ? style()->outlineSize() : 0; |
| |
| // We need to add in the relative position offsets of any inlines (including us) up to our |
| // containing block. |
| RenderBlock* cb = containingBlock(); |
| for (RenderObject* inlineFlow = this; inlineFlow && inlineFlow->isRenderInline() && inlineFlow != cb; |
| inlineFlow = inlineFlow->parent()) { |
| if (inlineFlow->style()->position() == RelativePosition && inlineFlow->hasLayer()) |
| toRenderInline(inlineFlow)->layer()->relativePositionOffset(left, top); |
| } |
| |
| IntRect r(-ow + left, -ow + top, boundingBox.width() + ow * 2, boundingBox.height() + ow * 2); |
| |
| if (cb->hasColumns()) |
| cb->adjustRectForColumns(r); |
| |
| if (cb->hasOverflowClip()) { |
| // cb->height() is inaccurate if we're in the middle of a layout of |cb|, so use the |
| // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint |
| // anyway if its size does change. |
| IntRect repaintRect(r); |
| repaintRect.move(-cb->layer()->scrolledContentOffset()); // For overflow:auto/scroll/hidden. |
| |
| IntRect boxRect(0, 0, cb->layer()->width(), cb->layer()->height()); |
| r = intersection(repaintRect, boxRect); |
| } |
| |
| // FIXME: need to ensure that we compute the correct repaint rect when the repaint container |
| // is an inline. |
| if (repaintContainer != this) |
| cb->computeRectForRepaint(repaintContainer, r); |
| |
| if (ow) { |
| for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (!curr->isText()) { |
| IntRect childRect = curr->rectWithOutlineForRepaint(repaintContainer, ow); |
| r.unite(childRect); |
| } |
| } |
| |
| if (continuation() && !continuation()->isInline()) { |
| IntRect contRect = continuation()->rectWithOutlineForRepaint(repaintContainer, ow); |
| r.unite(contRect); |
| } |
| } |
| |
| return r; |
| } |
| |
| IntRect RenderInline::rectWithOutlineForRepaint(RenderBoxModelObject* repaintContainer, int outlineWidth) |
| { |
| IntRect r(RenderBoxModelObject::rectWithOutlineForRepaint(repaintContainer, outlineWidth)); |
| for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (!curr->isText()) |
| r.unite(curr->rectWithOutlineForRepaint(repaintContainer, outlineWidth)); |
| } |
| return r; |
| } |
| |
| void RenderInline::computeRectForRepaint(RenderBoxModelObject* repaintContainer, IntRect& rect, bool fixed) |
| { |
| if (RenderView* v = view()) { |
| // LayoutState is only valid for root-relative repainting |
| if (v->layoutStateEnabled() && !repaintContainer) { |
| LayoutState* layoutState = v->layoutState(); |
| if (style()->position() == RelativePosition && layer()) |
| rect.move(layer()->relativePositionOffset()); |
| rect.move(layoutState->m_paintOffset); |
| if (layoutState->m_clipped) |
| rect.intersect(layoutState->m_clipRect); |
| return; |
| } |
| } |
| |
| if (repaintContainer == this) |
| return; |
| |
| bool containerSkipped; |
| RenderObject* o = container(repaintContainer, &containerSkipped); |
| if (!o) |
| return; |
| |
| IntPoint topLeft = rect.location(); |
| |
| if (o->isBlockFlow() && style()->position() != AbsolutePosition && style()->position() != FixedPosition) { |
| RenderBlock* cb = toRenderBlock(o); |
| if (cb->hasColumns()) { |
| IntRect repaintRect(topLeft, rect.size()); |
| cb->adjustRectForColumns(repaintRect); |
| topLeft = repaintRect.location(); |
| rect = repaintRect; |
| } |
| } |
| |
| if (style()->position() == RelativePosition && layer()) { |
| // Apply the relative position offset when invalidating a rectangle. The layer |
| // is translated, but the render box isn't, so we need to do this to get the |
| // right dirty rect. Since this is called from RenderObject::setStyle, the relative position |
| // flag on the RenderObject has been cleared, so use the one on the style(). |
| topLeft += layer()->relativePositionOffset(); |
| } |
| |
| // FIXME: We ignore the lightweight clipping rect that controls use, since if |o| is in mid-layout, |
| // its controlClipRect will be wrong. For overflow clip we use the values cached by the layer. |
| if (o->hasOverflowClip()) { |
| RenderBox* containerBox = toRenderBox(o); |
| |
| // o->height() is inaccurate if we're in the middle of a layout of |o|, so use the |
| // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint |
| // anyway if its size does change. |
| topLeft -= containerBox->layer()->scrolledContentOffset(); // For overflow:auto/scroll/hidden. |
| |
| IntRect repaintRect(topLeft, rect.size()); |
| IntRect boxRect(0, 0, containerBox->layer()->width(), containerBox->layer()->height()); |
| rect = intersection(repaintRect, boxRect); |
| if (rect.isEmpty()) |
| return; |
| } else |
| rect.setLocation(topLeft); |
| |
| if (containerSkipped) { |
| // If the repaintContainer is below o, then we need to map the rect into repaintContainer's coordinates. |
| IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); |
| rect.move(-containerOffset); |
| return; |
| } |
| |
| o->computeRectForRepaint(repaintContainer, rect, fixed); |
| } |
| |
| IntSize RenderInline::offsetFromContainer(RenderObject* container, const IntPoint& point) const |
| { |
| ASSERT(container == this->container()); |
| |
| IntSize offset; |
| if (isRelPositioned()) |
| offset += relativePositionOffset(); |
| |
| container->adjustForColumns(offset, point); |
| |
| if (container->hasOverflowClip()) |
| offset -= toRenderBox(container)->layer()->scrolledContentOffset(); |
| |
| return offset; |
| } |
| |
| void RenderInline::mapLocalToContainer(RenderBoxModelObject* repaintContainer, bool fixed, bool useTransforms, TransformState& transformState) const |
| { |
| if (repaintContainer == this) |
| return; |
| |
| if (RenderView *v = view()) { |
| if (v->layoutStateEnabled() && !repaintContainer) { |
| LayoutState* layoutState = v->layoutState(); |
| IntSize offset = layoutState->m_paintOffset; |
| if (style()->position() == RelativePosition && layer()) |
| offset += layer()->relativePositionOffset(); |
| transformState.move(offset); |
| return; |
| } |
| } |
| |
| bool containerSkipped; |
| RenderObject* o = container(repaintContainer, &containerSkipped); |
| if (!o) |
| return; |
| |
| IntPoint centerPoint = roundedIntPoint(transformState.mappedPoint()); |
| if (o->isBox() && o->style()->isFlippedBlocksWritingMode()) |
| transformState.move(toRenderBox(o)->flipForWritingModeIncludingColumns(roundedIntPoint(transformState.mappedPoint())) - centerPoint); |
| |
| IntSize containerOffset = offsetFromContainer(o, roundedIntPoint(transformState.mappedPoint())); |
| |
| bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); |
| if (useTransforms && shouldUseTransformFromContainer(o)) { |
| TransformationMatrix t; |
| getTransformFromContainer(o, containerOffset, t); |
| transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| } else |
| transformState.move(containerOffset.width(), containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| |
| if (containerSkipped) { |
| // There can't be a transform between repaintContainer and o, because transforms create containers, so it should be safe |
| // to just subtract the delta between the repaintContainer and o. |
| IntSize containerOffset = repaintContainer->offsetFromAncestorContainer(o); |
| transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| return; |
| } |
| |
| o->mapLocalToContainer(repaintContainer, fixed, useTransforms, transformState); |
| } |
| |
| void RenderInline::mapAbsoluteToLocalPoint(bool fixed, bool useTransforms, TransformState& transformState) const |
| { |
| // We don't expect this function to be called during layout. |
| ASSERT(!view() || !view()->layoutStateEnabled()); |
| |
| RenderObject* o = container(); |
| if (!o) |
| return; |
| |
| o->mapAbsoluteToLocalPoint(fixed, useTransforms, transformState); |
| |
| IntSize containerOffset = offsetFromContainer(o, IntPoint()); |
| |
| bool preserve3D = useTransforms && (o->style()->preserves3D() || style()->preserves3D()); |
| if (useTransforms && shouldUseTransformFromContainer(o)) { |
| TransformationMatrix t; |
| getTransformFromContainer(o, containerOffset, t); |
| transformState.applyTransform(t, preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| } else |
| transformState.move(-containerOffset.width(), -containerOffset.height(), preserve3D ? TransformState::AccumulateTransform : TransformState::FlattenTransform); |
| } |
| |
| void RenderInline::updateDragState(bool dragOn) |
| { |
| RenderBoxModelObject::updateDragState(dragOn); |
| if (continuation()) |
| continuation()->updateDragState(dragOn); |
| } |
| |
| void RenderInline::childBecameNonInline(RenderObject* child) |
| { |
| // We have to split the parent flow. |
| RenderBlock* newBox = containingBlock()->createAnonymousBlock(); |
| RenderBoxModelObject* oldContinuation = continuation(); |
| setContinuation(newBox); |
| RenderObject* beforeChild = child->nextSibling(); |
| children()->removeChildNode(this, child); |
| splitFlow(beforeChild, newBox, child, oldContinuation); |
| } |
| |
| void RenderInline::updateHitTestResult(HitTestResult& result, const IntPoint& point) |
| { |
| if (result.innerNode()) |
| return; |
| |
| Node* n = node(); |
| IntPoint 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 renderer's containing block. This will end up being the innerNonSharedNode. |
| RenderBlock* firstBlock = n->renderer()->containingBlock(); |
| |
| // Get our containing block. |
| RenderBox* block = containingBlock(); |
| localPoint.move(block->x() - firstBlock->x(), block->y() - firstBlock->y()); |
| } |
| |
| result.setInnerNode(n); |
| if (!result.innerNonSharedNode()) |
| result.setInnerNonSharedNode(n); |
| result.setLocalPoint(localPoint); |
| } |
| } |
| |
| void RenderInline::dirtyLineBoxes(bool fullLayout) |
| { |
| if (fullLayout) |
| m_lineBoxes.deleteLineBoxes(renderArena()); |
| else |
| m_lineBoxes.dirtyLineBoxes(); |
| } |
| |
| InlineFlowBox* RenderInline::createInlineFlowBox() |
| { |
| return new (renderArena()) InlineFlowBox(this); |
| } |
| |
| InlineFlowBox* RenderInline::createAndAppendInlineFlowBox() |
| { |
| InlineFlowBox* flowBox = createInlineFlowBox(); |
| m_lineBoxes.appendLineBox(flowBox); |
| return flowBox; |
| } |
| |
| int RenderInline::lineHeight(bool firstLine, LineDirectionMode /*direction*/, LinePositionMode /*linePositionMode*/) const |
| { |
| if (firstLine && document()->usesFirstLineRules()) { |
| RenderStyle* s = style(firstLine); |
| if (s != style()) |
| return s->computedLineHeight(); |
| } |
| |
| if (m_lineHeight == -1) |
| m_lineHeight = style()->computedLineHeight(); |
| |
| return m_lineHeight; |
| } |
| |
| int RenderInline::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const |
| { |
| const FontMetrics& fontMetrics = style(firstLine)->fontMetrics(); |
| return fontMetrics.ascent(baselineType) + (lineHeight(firstLine, direction, linePositionMode) - fontMetrics.height()) / 2; |
| } |
| |
| IntSize RenderInline::relativePositionedInlineOffset(const RenderBox* child) const |
| { |
| // FIXME: This function isn't right with mixed writing modes. |
| |
| ASSERT(isRelPositioned()); |
| if (!isRelPositioned()) |
| return IntSize(); |
| |
| // 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. |
| |
| IntSize logicalOffset; |
| int inlinePosition; |
| int blockPosition; |
| if (firstLineBox()) { |
| inlinePosition = lroundf(firstLineBox()->logicalLeft()); |
| blockPosition = firstLineBox()->logicalTop(); |
| } else { |
| inlinePosition = layer()->staticInlinePosition(); |
| blockPosition = layer()->staticBlockPosition(); |
| } |
| |
| if (!child->style()->hasStaticInlinePosition(style()->isHorizontalWritingMode())) |
| logicalOffset.setWidth(inlinePosition); |
| |
| // This is not terribly intuitive, but we have to match other browsers. Despite being a block display type inside |
| // an inline, we still keep our x locked to the left of the relative positioned inline. Arguably the correct |
| // behavior would be to go flush left to the block that contains the inline, but that isn't what other browsers |
| // do. |
| else if (!child->style()->isOriginalDisplayInlineType()) |
| // Avoid adding in the left border/padding of the containing block twice. Subtract it out. |
| logicalOffset.setWidth(inlinePosition - child->containingBlock()->borderAndPaddingLogicalLeft()); |
| |
| if (!child->style()->hasStaticBlockPosition(style()->isHorizontalWritingMode())) |
| logicalOffset.setHeight(blockPosition); |
| |
| return style()->isHorizontalWritingMode() ? logicalOffset : logicalOffset.transposedSize(); |
| } |
| |
| void RenderInline::imageChanged(WrappedImagePtr, const IntRect*) |
| { |
| if (!parent()) |
| return; |
| |
| // FIXME: We can do better. |
| repaint(); |
| } |
| |
| void RenderInline::addFocusRingRects(Vector<IntRect>& rects, int tx, int ty) |
| { |
| for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { |
| RootInlineBox* root = curr->root(); |
| int top = max(root->lineTop(), curr->logicalTop()); |
| int bottom = min(root->lineBottom(), curr->logicalBottom()); |
| IntRect rect(tx + curr->x(), ty + top, curr->logicalWidth(), bottom - top); |
| if (!rect.isEmpty()) |
| rects.append(rect); |
| } |
| |
| for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { |
| if (!curr->isText() && !curr->isListMarker()) { |
| FloatPoint pos(tx, ty); |
| // FIXME: This doesn't work correctly with transforms. |
| if (curr->hasLayer()) |
| pos = curr->localToAbsolute(); |
| else if (curr->isBox()) |
| pos.move(toRenderBox(curr)->x(), toRenderBox(curr)->y()); |
| curr->addFocusRingRects(rects, pos.x(), pos.y()); |
| } |
| } |
| |
| if (continuation()) { |
| if (continuation()->isInline()) |
| continuation()->addFocusRingRects(rects, |
| tx - containingBlock()->x() + continuation()->containingBlock()->x(), |
| ty - containingBlock()->y() + continuation()->containingBlock()->y()); |
| else |
| continuation()->addFocusRingRects(rects, |
| tx - containingBlock()->x() + toRenderBox(continuation())->x(), |
| ty - containingBlock()->y() + toRenderBox(continuation())->y()); |
| } |
| } |
| |
| void RenderInline::paintOutline(GraphicsContext* graphicsContext, int tx, int ty) |
| { |
| if (!hasOutline()) |
| return; |
| |
| RenderStyle* styleToUse = style(); |
| if (styleToUse->outlineStyleIsAuto() || hasOutlineAnnotation()) { |
| if (!theme()->supportsFocusRing(styleToUse)) { |
| // Only paint the focus ring by hand if the theme isn't able to draw the focus ring. |
| paintFocusRing(graphicsContext, tx, ty, styleToUse); |
| } |
| } |
| |
| if (styleToUse->outlineStyleIsAuto() || styleToUse->outlineStyle() == BNONE) |
| return; |
| |
| Vector<IntRect> rects; |
| |
| rects.append(IntRect()); |
| for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { |
| RootInlineBox* root = curr->root(); |
| int top = max(root->lineTop(), curr->logicalTop()); |
| int bottom = min(root->lineBottom(), curr->logicalBottom()); |
| rects.append(IntRect(curr->x(), top, curr->logicalWidth(), bottom - top)); |
| } |
| rects.append(IntRect()); |
| |
| for (unsigned i = 1; i < rects.size() - 1; i++) |
| paintOutlineForLine(graphicsContext, tx, ty, rects.at(i - 1), rects.at(i), rects.at(i + 1)); |
| } |
| |
| void RenderInline::paintOutlineForLine(GraphicsContext* graphicsContext, int tx, int ty, |
| const IntRect& lastline, const IntRect& thisline, const IntRect& nextline) |
| { |
| RenderStyle* styleToUse = style(); |
| int ow = styleToUse->outlineWidth(); |
| EBorderStyle os = styleToUse->outlineStyle(); |
| Color oc = styleToUse->visitedDependentColor(CSSPropertyOutlineColor); |
| |
| int offset = style()->outlineOffset(); |
| |
| int t = ty + thisline.y() - offset; |
| int l = tx + thisline.x() - offset; |
| int b = ty + thisline.maxY() + offset; |
| int r = tx + thisline.maxX() + offset; |
| |
| // left edge |
| drawLineForBoxSide(graphicsContext, |
| l - ow, |
| t - (lastline.isEmpty() || thisline.x() < lastline.x() || (lastline.maxX() - 1) <= thisline.x() ? ow : 0), |
| l, |
| b + (nextline.isEmpty() || thisline.x() <= nextline.x() || (nextline.maxX() - 1) <= thisline.x() ? ow : 0), |
| BSLeft, |
| oc, os, |
| (lastline.isEmpty() || thisline.x() < lastline.x() || (lastline.maxX() - 1) <= thisline.x() ? ow : -ow), |
| (nextline.isEmpty() || thisline.x() <= nextline.x() || (nextline.maxX() - 1) <= thisline.x() ? ow : -ow)); |
| |
| // right edge |
| drawLineForBoxSide(graphicsContext, |
| r, |
| t - (lastline.isEmpty() || lastline.maxX() < thisline.maxX() || (thisline.maxX() - 1) <= lastline.x() ? ow : 0), |
| r + ow, |
| b + (nextline.isEmpty() || nextline.maxX() <= thisline.maxX() || (thisline.maxX() - 1) <= nextline.x() ? ow : 0), |
| BSRight, |
| oc, os, |
| (lastline.isEmpty() || lastline.maxX() < thisline.maxX() || (thisline.maxX() - 1) <= lastline.x() ? ow : -ow), |
| (nextline.isEmpty() || nextline.maxX() <= thisline.maxX() || (thisline.maxX() - 1) <= nextline.x() ? ow : -ow)); |
| // upper edge |
| if (thisline.x() < lastline.x()) |
| drawLineForBoxSide(graphicsContext, |
| l - ow, |
| t - ow, |
| min(r+ow, (lastline.isEmpty() ? 1000000 : tx + lastline.x())), |
| t , |
| BSTop, oc, os, |
| ow, |
| (!lastline.isEmpty() && tx + lastline.x() + 1 < r + ow) ? -ow : ow); |
| |
| if (lastline.maxX() < thisline.maxX()) |
| drawLineForBoxSide(graphicsContext, |
| max(lastline.isEmpty() ? -1000000 : tx + lastline.maxX(), l - ow), |
| t - ow, |
| r + ow, |
| t , |
| BSTop, oc, os, |
| (!lastline.isEmpty() && l - ow < tx + lastline.maxX()) ? -ow : ow, |
| ow); |
| |
| // lower edge |
| if (thisline.x() < nextline.x()) |
| drawLineForBoxSide(graphicsContext, |
| l - ow, |
| b, |
| min(r + ow, !nextline.isEmpty() ? tx + nextline.x() + 1 : 1000000), |
| b + ow, |
| BSBottom, oc, os, |
| ow, |
| (!nextline.isEmpty() && tx + nextline.x() + 1 < r + ow) ? -ow : ow); |
| |
| if (nextline.maxX() < thisline.maxX()) |
| drawLineForBoxSide(graphicsContext, |
| max(!nextline.isEmpty() ? tx + nextline.maxX() : -1000000, l - ow), |
| b, |
| r + ow, |
| b + ow, |
| BSBottom, oc, os, |
| (!nextline.isEmpty() && l - ow < tx + nextline.maxX()) ? -ow : ow, |
| ow); |
| } |
| |
| #if ENABLE(DASHBOARD_SUPPORT) |
| void RenderInline::addDashboardRegions(Vector<DashboardRegionValue>& regions) |
| { |
| // Convert the style regions to absolute coordinates. |
| if (style()->visibility() != VISIBLE) |
| return; |
| |
| const Vector<StyleDashboardRegion>& styleRegions = style()->dashboardRegions(); |
| unsigned i, count = styleRegions.size(); |
| for (i = 0; i < count; i++) { |
| StyleDashboardRegion styleRegion = styleRegions[i]; |
| |
| IntRect linesBoundingBox = this->linesBoundingBox(); |
| int w = linesBoundingBox.width(); |
| int h = linesBoundingBox.height(); |
| |
| DashboardRegionValue region; |
| region.label = styleRegion.label; |
| region.bounds = IntRect(linesBoundingBox.x() + styleRegion.offset.left().value(), |
| linesBoundingBox.y() + styleRegion.offset.top().value(), |
| w - styleRegion.offset.left().value() - styleRegion.offset.right().value(), |
| h - styleRegion.offset.top().value() - styleRegion.offset.bottom().value()); |
| region.type = styleRegion.type; |
| |
| RenderObject* container = containingBlock(); |
| if (!container) |
| container = this; |
| |
| region.clip = region.bounds; |
| container->computeAbsoluteRepaintRect(region.clip); |
| if (region.clip.height() < 0) { |
| region.clip.setHeight(0); |
| region.clip.setWidth(0); |
| } |
| |
| FloatPoint absPos = container->localToAbsolute(); |
| region.bounds.setX(absPos.x() + region.bounds.x()); |
| region.bounds.setY(absPos.y() + region.bounds.y()); |
| |
| if (frame()) { |
| float pageScaleFactor = frame()->page()->chrome()->scaleFactor(); |
| if (pageScaleFactor != 1.0f) { |
| region.bounds.scale(pageScaleFactor); |
| region.clip.scale(pageScaleFactor); |
| } |
| } |
| |
| regions.append(region); |
| } |
| } |
| #endif |
| |
| } // namespace WebCore |