| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * Copyright (C) 2000 Dirk Mueller (mueller@kde.org) |
| * Copyright (C) 2004, 2006, 2007 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 "RenderReplaced.h" |
| |
| #include "GraphicsContext.h" |
| #include "RenderBlock.h" |
| #include "RenderLayer.h" |
| #include "RenderTheme.h" |
| #include "RenderView.h" |
| #include "VisiblePosition.h" |
| |
| using namespace std; |
| |
| namespace WebCore { |
| |
| typedef WTF::HashMap<const RenderReplaced*, IntRect> OverflowRectMap; |
| static OverflowRectMap* gOverflowRectMap = 0; |
| |
| const int cDefaultWidth = 300; |
| const int cDefaultHeight = 150; |
| |
| RenderReplaced::RenderReplaced(Node* node) |
| : RenderBox(node) |
| , m_intrinsicSize(cDefaultWidth, cDefaultHeight) |
| { |
| setReplaced(true); |
| } |
| |
| RenderReplaced::RenderReplaced(Node* node, const IntSize& intrinsicSize) |
| : RenderBox(node) |
| , m_intrinsicSize(intrinsicSize) |
| { |
| setReplaced(true); |
| } |
| |
| RenderReplaced::~RenderReplaced() |
| { |
| if (replacedHasOverflow()) |
| gOverflowRectMap->remove(this); |
| } |
| |
| void RenderReplaced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| RenderBox::styleDidChange(diff, oldStyle); |
| |
| bool hadStyle = (oldStyle != 0); |
| float oldZoom = hadStyle ? oldStyle->effectiveZoom() : RenderStyle::initialZoom(); |
| if (hadStyle && style() && style()->effectiveZoom() != oldZoom) |
| intrinsicSizeChanged(); |
| } |
| |
| void RenderReplaced::layout() |
| { |
| ASSERT(needsLayout()); |
| |
| LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); |
| |
| setHeight(minimumReplacedHeight()); |
| |
| calcWidth(); |
| calcHeight(); |
| adjustOverflowForBoxShadowAndReflect(); |
| |
| repainter.repaintAfterLayout(); |
| |
| setNeedsLayout(false); |
| } |
| |
| void RenderReplaced::intrinsicSizeChanged() |
| { |
| int scaledWidth = static_cast<int>(cDefaultWidth * style()->effectiveZoom()); |
| int scaledHeight = static_cast<int>(cDefaultHeight * style()->effectiveZoom()); |
| m_intrinsicSize = IntSize(scaledWidth, scaledHeight); |
| setNeedsLayoutAndPrefWidthsRecalc(); |
| } |
| |
| void RenderReplaced::paint(PaintInfo& paintInfo, int tx, int ty) |
| { |
| if (!shouldPaint(paintInfo, tx, ty)) |
| return; |
| |
| tx += x(); |
| ty += y(); |
| |
| if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) |
| paintBoxDecorations(paintInfo, tx, ty); |
| |
| if (paintInfo.phase == PaintPhaseMask) { |
| paintMask(paintInfo, tx, ty); |
| return; |
| } |
| |
| if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) |
| paintOutline(paintInfo.context, tx, ty, width(), height(), style()); |
| |
| if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection) |
| return; |
| |
| if (!shouldPaintWithinRoot(paintInfo)) |
| return; |
| |
| bool drawSelectionTint = selectionState() != SelectionNone && !document()->printing(); |
| if (paintInfo.phase == PaintPhaseSelection) { |
| if (selectionState() == SelectionNone) |
| return; |
| drawSelectionTint = false; |
| } |
| |
| if (style()->hasBorderRadius()) { |
| // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. |
| paintInfo.context->save(); |
| |
| IntSize topLeft, topRight, bottomLeft, bottomRight; |
| IntRect borderRect = IntRect(tx, ty, width(), height()); |
| style()->getBorderRadiiForRect(borderRect, topLeft, topRight, bottomLeft, bottomRight); |
| |
| paintInfo.context->addRoundedRectClip(borderRect, topLeft, topRight, bottomLeft, bottomRight); |
| } |
| |
| paintReplaced(paintInfo, tx, ty); |
| |
| if (style()->hasBorderRadius()) |
| paintInfo.context->restore(); |
| |
| // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of |
| // surrounding content. |
| if (drawSelectionTint) { |
| IntRect selectionPaintingRect = localSelectionRect(); |
| selectionPaintingRect.move(tx, ty); |
| paintInfo.context->fillRect(selectionPaintingRect, selectionBackgroundColor()); |
| } |
| } |
| |
| bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, int& tx, int& ty) |
| { |
| if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline |
| && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask) |
| return false; |
| |
| if (!shouldPaintWithinRoot(paintInfo)) |
| return false; |
| |
| // if we're invisible or haven't received a layout yet, then just bail. |
| if (style()->visibility() != VISIBLE) |
| return false; |
| |
| int currentTX = tx + x(); |
| int currentTY = ty + y(); |
| |
| // Early exit if the element touches the edges. |
| int top = currentTY + overflowTop(); |
| int bottom = currentTY + overflowHeight(); |
| if (isSelected() && m_inlineBoxWrapper) { |
| int selTop = ty + m_inlineBoxWrapper->root()->selectionTop(); |
| int selBottom = ty + selTop + m_inlineBoxWrapper->root()->selectionHeight(); |
| top = min(selTop, top); |
| bottom = max(selBottom, bottom); |
| } |
| |
| int os = 2 * maximalOutlineSize(paintInfo.phase); |
| if (currentTX + overflowLeft() >= paintInfo.rect.right() + os || currentTX + overflowWidth() <= paintInfo.rect.x() - os) |
| return false; |
| if (top >= paintInfo.rect.bottom() + os || bottom <= paintInfo.rect.y() - os) |
| return false; |
| |
| return true; |
| } |
| |
| void RenderReplaced::calcPrefWidths() |
| { |
| ASSERT(prefWidthsDirty()); |
| |
| int paddingAndBorders = paddingLeft() + paddingRight() + borderLeft() + borderRight(); |
| int width = calcReplacedWidth(false) + paddingAndBorders; |
| |
| if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) |
| width = min(width, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? paddingAndBorders : 0)); |
| |
| if (style()->width().isPercent() || (style()->width().isAuto() && style()->height().isPercent())) { |
| m_minPrefWidth = 0; |
| m_maxPrefWidth = width; |
| } else |
| m_minPrefWidth = m_maxPrefWidth = width; |
| |
| setPrefWidthsDirty(false); |
| } |
| |
| int RenderReplaced::lineHeight(bool, bool) const |
| { |
| return height() + marginTop() + marginBottom(); |
| } |
| |
| int RenderReplaced::baselinePosition(bool, bool) const |
| { |
| return height() + marginTop() + marginBottom(); |
| } |
| |
| unsigned RenderReplaced::caretMaxRenderedOffset() const |
| { |
| return 1; |
| } |
| |
| VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point) |
| { |
| InlineBox* box = inlineBoxWrapper(); |
| if (!box) |
| return createVisiblePosition(0, DOWNSTREAM); |
| |
| // FIXME: This code is buggy if the replaced element is relative positioned. |
| |
| RootInlineBox* root = box->root(); |
| |
| int top = root->topOverflow(); |
| int bottom = root->nextRootBox() ? root->nextRootBox()->topOverflow() : root->bottomOverflow(); |
| |
| if (point.y() + y() < top) |
| return createVisiblePosition(caretMinOffset(), DOWNSTREAM); // coordinates are above |
| |
| if (point.y() + y() >= bottom) |
| return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); // coordinates are below |
| |
| if (node()) { |
| if (point.x() <= width() / 2) |
| return createVisiblePosition(0, DOWNSTREAM); |
| return createVisiblePosition(1, DOWNSTREAM); |
| } |
| |
| return RenderBox::positionForPoint(point); |
| } |
| |
| IntRect RenderReplaced::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent) |
| { |
| ASSERT(!needsLayout()); |
| |
| if (!isSelected()) |
| return IntRect(); |
| |
| IntRect rect = localSelectionRect(); |
| if (clipToVisibleContent) |
| computeRectForRepaint(repaintContainer, rect); |
| else |
| rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox(); |
| |
| return rect; |
| } |
| |
| IntRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const |
| { |
| if (checkWhetherSelected && !isSelected()) |
| return IntRect(); |
| |
| if (!m_inlineBoxWrapper) |
| // We're a block-level replaced element. Just return our own dimensions. |
| return IntRect(0, 0, width(), height()); |
| |
| RenderBlock* cb = containingBlock(); |
| if (!cb) |
| return IntRect(); |
| |
| RootInlineBox* root = m_inlineBoxWrapper->root(); |
| return IntRect(0, root->selectionTop() - y(), width(), root->selectionHeight()); |
| } |
| |
| void RenderReplaced::setSelectionState(SelectionState s) |
| { |
| RenderBox::setSelectionState(s); |
| if (m_inlineBoxWrapper) { |
| RootInlineBox* line = m_inlineBoxWrapper->root(); |
| if (line) |
| line->setHasSelectedChildren(isSelected()); |
| } |
| |
| containingBlock()->setSelectionState(s); |
| } |
| |
| bool RenderReplaced::isSelected() const |
| { |
| SelectionState s = selectionState(); |
| if (s == SelectionNone) |
| return false; |
| if (s == SelectionInside) |
| return true; |
| |
| int selectionStart, selectionEnd; |
| selectionStartEnd(selectionStart, selectionEnd); |
| if (s == SelectionStart) |
| return selectionStart == 0; |
| |
| int end = node()->hasChildNodes() ? node()->childNodeCount() : 1; |
| if (s == SelectionEnd) |
| return selectionEnd == end; |
| if (s == SelectionBoth) |
| return selectionStart == 0 && selectionEnd == end; |
| |
| ASSERT(0); |
| return false; |
| } |
| |
| IntSize RenderReplaced::intrinsicSize() const |
| { |
| return m_intrinsicSize; |
| } |
| |
| void RenderReplaced::setIntrinsicSize(const IntSize& size) |
| { |
| m_intrinsicSize = size; |
| } |
| |
| void RenderReplaced::adjustOverflowForBoxShadowAndReflect() |
| { |
| IntRect overflow; |
| for (ShadowData* boxShadow = style()->boxShadow(); boxShadow; boxShadow = boxShadow->next) { |
| IntRect shadow = borderBoxRect(); |
| shadow.move(boxShadow->x, boxShadow->y); |
| shadow.inflate(boxShadow->blur); |
| overflow.unite(shadow); |
| } |
| |
| // Now that we have an overflow rect including shadow, let's make sure that |
| // the reflection (which can also include the shadow) is also included. |
| if (hasReflection()) { |
| if (overflow.isEmpty()) |
| overflow = borderBoxRect(); |
| overflow.unite(reflectedRect(overflow)); |
| } |
| |
| if (!overflow.isEmpty()) { |
| if (!gOverflowRectMap) |
| gOverflowRectMap = new OverflowRectMap(); |
| overflow.unite(borderBoxRect()); |
| gOverflowRectMap->set(this, overflow); |
| setReplacedHasOverflow(true); |
| } else if (replacedHasOverflow()) { |
| gOverflowRectMap->remove(this); |
| setReplacedHasOverflow(false); |
| } |
| } |
| |
| int RenderReplaced::overflowHeight(bool) const |
| { |
| if (replacedHasOverflow()) { |
| IntRect *r = &gOverflowRectMap->find(this)->second; |
| return r->height() + r->y(); |
| } |
| |
| return height(); |
| } |
| |
| int RenderReplaced::overflowWidth(bool) const |
| { |
| if (replacedHasOverflow()) { |
| IntRect *r = &gOverflowRectMap->find(this)->second; |
| return r->width() + r->x(); |
| } |
| |
| return width(); |
| } |
| |
| int RenderReplaced::overflowLeft(bool) const |
| { |
| if (replacedHasOverflow()) |
| return gOverflowRectMap->get(this).x(); |
| |
| return 0; |
| } |
| |
| int RenderReplaced::overflowTop(bool) const |
| { |
| if (replacedHasOverflow()) |
| return gOverflowRectMap->get(this).y(); |
| |
| return 0; |
| } |
| |
| IntRect RenderReplaced::overflowRect(bool) const |
| { |
| if (replacedHasOverflow()) |
| return gOverflowRectMap->find(this)->second; |
| |
| return borderBoxRect(); |
| } |
| |
| IntRect RenderReplaced::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) |
| { |
| if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent()) |
| return IntRect(); |
| |
| // The selectionRect can project outside of the overflowRect, so take their union |
| // for repainting to avoid selection painting glitches. |
| IntRect r = unionRect(localSelectionRect(false), overflowRect(false)); |
| |
| RenderView* v = view(); |
| if (v) { |
| // FIXME: layoutDelta needs to be applied in parts before/after transforms and |
| // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 |
| r.move(v->layoutDelta()); |
| } |
| |
| if (style()) { |
| if (style()->hasAppearance()) |
| // The theme may wish to inflate the rect used when repainting. |
| theme()->adjustRepaintRect(this, r); |
| if (v) |
| r.inflate(style()->outlineSize()); |
| } |
| computeRectForRepaint(repaintContainer, r); |
| return r; |
| } |
| |
| } |