/* * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2000 Dirk Mueller (mueller@kde.org) * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved. * Copyright (C) 2009 Google Inc. All rights reserved. * Copyright (C) 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * 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 "sky/engine/core/rendering/RenderObject.h" #include #include "gen/sky/platform/RuntimeEnabledFeatures.h" #include "sky/engine/core/css/resolver/StyleResolver.h" #include "sky/engine/core/dom/ElementTraversal.h" #include "sky/engine/core/dom/Range.h" #include "sky/engine/core/dom/StyleEngine.h" #include "sky/engine/core/dom/shadow/ShadowRoot.h" #include "sky/engine/core/editing/EditingBoundary.h" #include "sky/engine/core/editing/htmlediting.h" #include "sky/engine/core/frame/FrameView.h" #include "sky/engine/core/frame/LocalFrame.h" #include "sky/engine/core/frame/Settings.h" #include "sky/engine/core/html/HTMLElement.h" #include "sky/engine/core/page/Page.h" #include "sky/engine/core/rendering/HitTestResult.h" #include "sky/engine/core/rendering/RenderFlexibleBox.h" #include "sky/engine/core/rendering/RenderGeometryMap.h" #include "sky/engine/core/rendering/RenderInline.h" #include "sky/engine/core/rendering/RenderLayer.h" #include "sky/engine/core/rendering/RenderObjectInlines.h" #include "sky/engine/core/rendering/RenderParagraph.h" #include "sky/engine/core/rendering/RenderText.h" #include "sky/engine/core/rendering/RenderTheme.h" #include "sky/engine/core/rendering/RenderView.h" #include "sky/engine/core/rendering/style/ShadowList.h" #include "sky/engine/platform/JSONValues.h" #include "sky/engine/platform/Partitions.h" #include "sky/engine/platform/TraceEvent.h" #include "sky/engine/platform/geometry/TransformState.h" #include "sky/engine/platform/graphics/GraphicsContext.h" #include "sky/engine/wtf/RefCountedLeakCounter.h" #include "sky/engine/wtf/text/StringBuilder.h" #include "sky/engine/wtf/text/WTFString.h" #ifndef NDEBUG #include #endif namespace blink { #if ENABLE(ASSERT) RenderObject::SetLayoutNeededForbiddenScope::SetLayoutNeededForbiddenScope(RenderObject& renderObject) : m_renderObject(renderObject) , m_preexistingForbidden(m_renderObject.isSetNeedsLayoutForbidden()) { m_renderObject.setNeedsLayoutIsForbidden(true); } RenderObject::SetLayoutNeededForbiddenScope::~SetLayoutNeededForbiddenScope() { m_renderObject.setNeedsLayoutIsForbidden(m_preexistingForbidden); } #endif struct SameSizeAsRenderObject { virtual ~SameSizeAsRenderObject() { } // Allocate vtable pointer. void* pointers[5]; #if ENABLE(ASSERT) unsigned m_debugBitfields : 2; #if ENABLE(OILPAN) unsigned m_oilpanBitfields : 1; #endif #endif unsigned m_bitfields; }; COMPILE_ASSERT(sizeof(RenderObject) == sizeof(SameSizeAsRenderObject), RenderObject_should_stay_small); bool RenderObject::s_affectsParentBlock = false; #if !ENABLE(OILPAN) void* RenderObject::operator new(size_t sz) { ASSERT(isMainThread()); return partitionAlloc(Partitions::getRenderingPartition(), sz); } void RenderObject::operator delete(void* ptr) { ASSERT(isMainThread()); partitionFree(ptr); } #endif RenderObject* RenderObject::createObject(Element* element, RenderStyle* style) { ASSERT(isAllowedToModifyRenderTreeStructure(element->document())); switch (style->display()) { case NONE: return 0; case INLINE: return new RenderInline(element); case PARAGRAPH: return new RenderParagraph(element); case FLEX: case INLINE_FLEX: return new RenderFlexibleBox(element); } return 0; } DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, renderObjectCounter, ("RenderObject")); unsigned RenderObject::s_instanceCount = 0; RenderObject::RenderObject(Node* node) : m_style(nullptr) , m_node(node) , m_parent(nullptr) , m_previous(nullptr) , m_next(nullptr) #if ENABLE(ASSERT) , m_setNeedsLayoutForbidden(false) #if ENABLE(OILPAN) , m_didCallDestroy(false) #endif #endif , m_bitfields(node) { #ifndef NDEBUG renderObjectCounter.increment(); #endif ++s_instanceCount; } RenderObject::~RenderObject() { #if ENABLE(OILPAN) ASSERT(m_didCallDestroy); #endif #ifndef NDEBUG renderObjectCounter.decrement(); #endif --s_instanceCount; } String RenderObject::debugName() const { StringBuilder name; name.append(renderName()); if (Node* node = this->node()) { name.append(' '); name.append(node->debugName()); } return name.toString(); } bool RenderObject::isDescendantOf(const RenderObject* obj) const { for (const RenderObject* r = this; r; r = r->m_parent) { if (r == obj) return true; } return false; } void RenderObject::addChild(RenderObject* newChild, RenderObject* beforeChild) { ASSERT(isAllowedToModifyRenderTreeStructure(document())); RenderObjectChildList* children = virtualChildren(); ASSERT(children); children->insertChildNode(this, newChild, beforeChild); } void RenderObject::removeChild(RenderObject* oldChild) { ASSERT(isAllowedToModifyRenderTreeStructure(document())); RenderObjectChildList* children = virtualChildren(); ASSERT(children); if (!children) return; children->removeChildNode(this, oldChild); } RenderObject* RenderObject::nextInPreOrder() const { if (RenderObject* o = slowFirstChild()) return o; return nextInPreOrderAfterChildren(); } RenderObject* RenderObject::nextInPreOrderAfterChildren() const { RenderObject* o = nextSibling(); if (!o) { o = parent(); while (o && !o->nextSibling()) o = o->parent(); if (o) o = o->nextSibling(); } return o; } RenderObject* RenderObject::nextInPreOrder(const RenderObject* stayWithin) const { if (RenderObject* o = slowFirstChild()) return o; return nextInPreOrderAfterChildren(stayWithin); } RenderObject* RenderObject::nextInPreOrderAfterChildren(const RenderObject* stayWithin) const { if (this == stayWithin) return 0; const RenderObject* current = this; RenderObject* next = current->nextSibling(); for (; !next; next = current->nextSibling()) { current = current->parent(); if (!current || current == stayWithin) return 0; } return next; } RenderObject* RenderObject::previousInPreOrder() const { if (RenderObject* o = previousSibling()) { while (RenderObject* lastChild = o->slowLastChild()) o = lastChild; return o; } return parent(); } RenderObject* RenderObject::previousInPreOrder(const RenderObject* stayWithin) const { if (this == stayWithin) return 0; return previousInPreOrder(); } RenderObject* RenderObject::childAt(unsigned index) const { RenderObject* child = slowFirstChild(); for (unsigned i = 0; child && i < index; i++) child = child->nextSibling(); return child; } RenderObject* RenderObject::lastLeafChild() const { RenderObject* r = slowLastChild(); while (r) { RenderObject* n = 0; n = r->slowLastChild(); if (!n) break; r = n; } return r; } static void addLayers(RenderObject* obj, RenderLayer* parentLayer, RenderObject*& newObject, RenderLayer*& beforeChild) { if (obj->hasLayer()) { if (!beforeChild && newObject) { // We need to figure out the layer that follows newObject. We only do // this the first time we find a child layer, and then we update the // pointer values for newObject and beforeChild used by everyone else. beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject); newObject = 0; } parentLayer->addChild(toRenderBox(obj)->layer(), beforeChild); return; } for (RenderObject* curr = obj->slowFirstChild(); curr; curr = curr->nextSibling()) addLayers(curr, parentLayer, newObject, beforeChild); } void RenderObject::addLayers(RenderLayer* parentLayer) { if (!parentLayer) return; RenderObject* object = this; RenderLayer* beforeChild = 0; blink::addLayers(this, parentLayer, object, beforeChild); } void RenderObject::removeLayers(RenderLayer* parentLayer) { if (!parentLayer) return; if (hasLayer()) { parentLayer->removeChild(toRenderBox(this)->layer()); return; } for (RenderObject* curr = slowFirstChild(); curr; curr = curr->nextSibling()) curr->removeLayers(parentLayer); } void RenderObject::moveLayers(RenderLayer* oldParent, RenderLayer* newParent) { if (!newParent) return; if (hasLayer()) { RenderLayer* layer = toRenderBox(this)->layer(); ASSERT(oldParent == layer->parent()); if (oldParent) oldParent->removeChild(layer); newParent->addChild(layer); return; } for (RenderObject* curr = slowFirstChild(); curr; curr = curr->nextSibling()) curr->moveLayers(oldParent, newParent); } RenderLayer* RenderObject::findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, bool checkParent) { // Error check the parent layer passed in. If it's null, we can't find anything. if (!parentLayer) return 0; // Step 1: If our layer is a child of the desired parent, then return our layer. RenderLayer* ourLayer = hasLayer() ? toRenderBox(this)->layer() : 0; if (ourLayer && ourLayer->parent() == parentLayer) return ourLayer; // Step 2: If we don't have a layer, or our layer is the desired parent, then descend // into our siblings trying to find the next layer whose parent is the desired parent. if (!ourLayer || ourLayer == parentLayer) { for (RenderObject* curr = startPoint ? startPoint->nextSibling() : slowFirstChild(); curr; curr = curr->nextSibling()) { RenderLayer* nextLayer = curr->findNextLayer(parentLayer, 0, false); if (nextLayer) return nextLayer; } } // Step 3: If our layer is the desired parent layer, then we're finished. We didn't // find anything. if (parentLayer == ourLayer) return 0; // Step 4: If |checkParent| is set, climb up to our parent and check its siblings that // follow us to see if we can locate a layer. if (checkParent && parent()) return parent()->findNextLayer(parentLayer, this, true); return 0; } RenderLayer* RenderObject::enclosingLayer() const { for (const RenderObject* current = this; current; current = current->parent()) { if (current->hasLayer()) return toRenderBox(current)->layer(); } // FIXME: We should remove the one caller that triggers this case and make // this function return a reference. ASSERT(!m_parent && !isRenderView()); return 0; } RenderBox* RenderObject::enclosingBox() const { RenderObject* curr = const_cast(this); while (curr) { if (curr->isBox()) return toRenderBox(curr); curr = curr->parent(); } ASSERT_NOT_REACHED(); return 0; } RenderBoxModelObject* RenderObject::enclosingBoxModelObject() const { RenderObject* curr = const_cast(this); while (curr) { if (curr->isBoxModelObject()) return toRenderBoxModelObject(curr); curr = curr->parent(); } ASSERT_NOT_REACHED(); return 0; } bool RenderObject::skipInvalidationWhenLaidOutChildren() const { if (!neededLayoutBecauseOfChildren()) return false; // SVG renderers need to be invalidated when their children are laid out. // RenderBlocks with line boxes are responsible to invalidate them so we can't ignore them. if (isRenderParagraph() && toRenderParagraph(this)->firstLineBox()) return false; return rendererHasNoBoxEffect(); } RenderBlock* RenderObject::firstLineBlock() const { return 0; } static inline bool objectIsRelayoutBoundary(const RenderObject* object) { if (!object->hasOverflowClip()) return false; if (object->style()->width().isIntrinsicOrAuto() || object->style()->height().isIntrinsicOrAuto() || object->style()->height().isPercent()) return false; return true; } void RenderObject::markContainingBlocksForLayout(bool scheduleRelayout, RenderObject* newRoot, SubtreeLayoutScope* layouter) { ASSERT(!scheduleRelayout || !newRoot); ASSERT(!isSetNeedsLayoutForbidden()); ASSERT(!layouter || this != layouter->root()); RenderObject* object = container(); RenderObject* last = this; bool simplifiedNormalFlowLayout = needsSimplifiedNormalFlowLayout() && !selfNeedsLayout() && !normalChildNeedsLayout(); while (object) { if (object->selfNeedsLayout()) return; // Don't mark the outermost object of an unrooted subtree. That object will be // marked when the subtree is added to the document. RenderObject* container = object->container(); if (!container && !object->isRenderView()) return; if (!last->isText() && last->style()->hasOutOfFlowPosition()) { bool willSkipRelativelyPositionedInlines = !object->isRenderBlock(); // Skip relatively positioned inlines and anonymous blocks to get to the enclosing RenderBlock. while (object && !object->isRenderBlock()) object = object->container(); if (!object || object->posChildNeedsLayout()) return; if (willSkipRelativelyPositionedInlines) container = object->container(); object->setPosChildNeedsLayout(true); simplifiedNormalFlowLayout = true; ASSERT(!object->isSetNeedsLayoutForbidden()); } else if (simplifiedNormalFlowLayout) { if (object->needsSimplifiedNormalFlowLayout()) return; object->setNeedsSimplifiedNormalFlowLayout(true); ASSERT(!object->isSetNeedsLayoutForbidden()); } else { if (object->normalChildNeedsLayout()) return; object->setNormalChildNeedsLayout(true); ASSERT(!object->isSetNeedsLayoutForbidden()); } if (layouter) { layouter->addRendererToLayout(object); if (object == layouter->root()) return; } if (object == newRoot) return; last = object; if (scheduleRelayout && objectIsRelayoutBoundary(last)) break; object = container; } if (scheduleRelayout) last->scheduleRelayout(); } #if ENABLE(ASSERT) void RenderObject::checkBlockPositionedObjectsNeedLayout() { ASSERT(!needsLayout()); if (isRenderBlock()) toRenderBlock(this)->checkPositionedObjectsNeedLayout(); } #endif void RenderObject::setPreferredLogicalWidthsDirty(MarkingBehavior markParents) { m_bitfields.setPreferredLogicalWidthsDirty(true); if (markParents == MarkContainingBlockChain && (isText() || !style()->hasOutOfFlowPosition())) invalidateContainerPreferredLogicalWidths(); } void RenderObject::clearPreferredLogicalWidthsDirty() { m_bitfields.setPreferredLogicalWidthsDirty(false); } void RenderObject::invalidateContainerPreferredLogicalWidths() { // In order to avoid pathological behavior when inlines are deeply nested, we do include them // in the chain that we mark dirty (even though they're kind of irrelevant). RenderObject* o = container(); while (o && !o->preferredLogicalWidthsDirty()) { // Don't invalidate the outermost object of an unrooted subtree. That object will be // invalidated when the subtree is added to the document. RenderObject* container = o->container(); if (!container && !o->isRenderView()) break; o->m_bitfields.setPreferredLogicalWidthsDirty(true); if (o->style()->hasOutOfFlowPosition()) // A positioned object has no effect on the min/max width of its containing block ever. // We can optimize this case and not go up any further. break; o = container; } } RenderBlock* RenderObject::containingBlock() const { RenderObject* o = parent(); if (!isText() && m_style->position() == AbsolutePosition) { while (o) { // For relpositioned inlines, we return the nearest non-anonymous enclosing block. We don't try // to return the inline itself. This allows us to avoid having a positioned objects // list in all RenderInlines and lets us return a strongly-typed RenderBlock* result // from this method. The container() method can actually be used to obtain the // inline directly. if (o->style()->position() != StaticPosition && (!o->isInline() || o->isReplaced())) break; if (o->canContainAbsolutePositionObjects()) break; if (o->style()->hasInFlowPosition() && o->isInline() && !o->isReplaced()) { o = o->containingBlock(); break; } o = o->parent(); } if (o && !o->isRenderBlock()) o = o->containingBlock(); } else { while (o && ((o->isInline() && !o->isReplaced()) || !o->isRenderBlock())) o = o->parent(); } if (!o || !o->isRenderBlock()) return 0; // This can still happen in case of an orphaned tree return toRenderBlock(o); } void RenderObject::drawLineForBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, BoxSide side, Color color, EBorderStyle style, int adjacentWidth1, int adjacentWidth2, bool antialias) { int thickness; int length; if (side == BSTop || side == BSBottom) { thickness = y2 - y1; length = x2 - x1; } else { thickness = x2 - x1; length = y2 - y1; } // FIXME: We really would like this check to be an ASSERT as we don't want to draw empty borders. However // nothing guarantees that the following recursive calls to drawLineForBoxSide will have non-null dimensions. if (!thickness || !length) return; if (style == DOUBLE && thickness < 3) style = SOLID; switch (style) { case BNONE: case BHIDDEN: return; case DOTTED: case DASHED: drawDashedOrDottedBoxSide(graphicsContext, x1, y1, x2, y2, side, color, thickness, style, antialias); break; case DOUBLE: drawDoubleBoxSide(graphicsContext, x1, y1, x2, y2, length, side, color, thickness, adjacentWidth1, adjacentWidth2, antialias); break; case RIDGE: case GROOVE: drawRidgeOrGrooveBoxSide(graphicsContext, x1, y1, x2, y2, side, color, style, adjacentWidth1, adjacentWidth2, antialias); break; case INSET: // FIXME: Maybe we should lighten the colors on one side like Firefox. // https://bugs.webkit.org/show_bug.cgi?id=58608 if (side == BSTop || side == BSLeft) color = color.dark(); // fall through case OUTSET: if (style == OUTSET && (side == BSBottom || side == BSRight)) color = color.dark(); // fall through case SOLID: drawSolidBoxSide(graphicsContext, x1, y1, x2, y2, side, color, adjacentWidth1, adjacentWidth2, antialias); break; } } void RenderObject::drawDashedOrDottedBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, BoxSide side, Color color, int thickness, EBorderStyle style, bool antialias) { if (thickness <= 0) return; bool wasAntialiased = graphicsContext->shouldAntialias(); StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle(); graphicsContext->setShouldAntialias(antialias); graphicsContext->setStrokeColor(color); graphicsContext->setStrokeThickness(thickness); graphicsContext->setStrokeStyle(style == DASHED ? DashedStroke : DottedStroke); switch (side) { case BSBottom: case BSTop: graphicsContext->drawLine(IntPoint(x1, (y1 + y2) / 2), IntPoint(x2, (y1 + y2) / 2)); break; case BSRight: case BSLeft: graphicsContext->drawLine(IntPoint((x1 + x2) / 2, y1), IntPoint((x1 + x2) / 2, y2)); break; } graphicsContext->setShouldAntialias(wasAntialiased); graphicsContext->setStrokeStyle(oldStrokeStyle); } void RenderObject::drawDoubleBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, int length, BoxSide side, Color color, int thickness, int adjacentWidth1, int adjacentWidth2, bool antialias) { int thirdOfThickness = (thickness + 1) / 3; ASSERT(thirdOfThickness); if (!adjacentWidth1 && !adjacentWidth2) { StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle(); graphicsContext->setStrokeStyle(NoStroke); graphicsContext->setFillColor(color); bool wasAntialiased = graphicsContext->shouldAntialias(); graphicsContext->setShouldAntialias(antialias); switch (side) { case BSTop: case BSBottom: graphicsContext->drawRect(IntRect(x1, y1, length, thirdOfThickness)); graphicsContext->drawRect(IntRect(x1, y2 - thirdOfThickness, length, thirdOfThickness)); break; case BSLeft: case BSRight: // FIXME: Why do we offset the border by 1 in this case but not the other one? if (length > 1) { graphicsContext->drawRect(IntRect(x1, y1 + 1, thirdOfThickness, length - 1)); graphicsContext->drawRect(IntRect(x2 - thirdOfThickness, y1 + 1, thirdOfThickness, length - 1)); } break; } graphicsContext->setShouldAntialias(wasAntialiased); graphicsContext->setStrokeStyle(oldStrokeStyle); return; } int adjacent1BigThird = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 3; int adjacent2BigThird = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 3; switch (side) { case BSTop: drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0), y1, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness, side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0), y2 - thirdOfThickness, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y2, side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); break; case BSLeft: drawLineForBoxSide(graphicsContext, x1, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0), x1 + thirdOfThickness, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0), x2, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); break; case BSBottom: drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0), y1, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness, side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0), y2 - thirdOfThickness, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y2, side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); break; case BSRight: drawLineForBoxSide(graphicsContext, x1, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0), x1 + thirdOfThickness, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0), x2, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias); break; default: break; } } void RenderObject::drawRidgeOrGrooveBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, BoxSide side, Color color, EBorderStyle style, int adjacentWidth1, int adjacentWidth2, bool antialias) { EBorderStyle s1; EBorderStyle s2; if (style == GROOVE) { s1 = INSET; s2 = OUTSET; } else { s1 = OUTSET; s2 = INSET; } int adjacent1BigHalf = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 2; int adjacent2BigHalf = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 2; switch (side) { case BSTop: drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1, 0) / 2, y1, x2 - std::max(-adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2, side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias); drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(adjacentWidth2 + 1, 0) / 2, y2, side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias); break; case BSLeft: drawLineForBoxSide(graphicsContext, x1, y1 + std::max(-adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(-adjacentWidth2, 0) / 2, side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias); drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(adjacentWidth2 + 1, 0) / 2, side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias); break; case BSBottom: drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1, 0) / 2, y1, x2 - std::max(adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2, side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias); drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(-adjacentWidth2 + 1, 0) / 2, y2, side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias); break; case BSRight: drawLineForBoxSide(graphicsContext, x1, y1 + std::max(adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(adjacentWidth2, 0) / 2, side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias); drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(-adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(-adjacentWidth2 + 1, 0) / 2, side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias); break; } } void RenderObject::drawSolidBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2, BoxSide side, Color color, int adjacentWidth1, int adjacentWidth2, bool antialias) { StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle(); graphicsContext->setStrokeStyle(NoStroke); graphicsContext->setFillColor(color); ASSERT(x2 >= x1); ASSERT(y2 >= y1); if (!adjacentWidth1 && !adjacentWidth2) { // Turn off antialiasing to match the behavior of drawConvexPolygon(); // this matters for rects in transformed contexts. bool wasAntialiased = graphicsContext->shouldAntialias(); graphicsContext->setShouldAntialias(antialias); graphicsContext->drawRect(IntRect(x1, y1, x2 - x1, y2 - y1)); graphicsContext->setShouldAntialias(wasAntialiased); graphicsContext->setStrokeStyle(oldStrokeStyle); return; } FloatPoint quad[4]; switch (side) { case BSTop: quad[0] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y1); quad[1] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y2); quad[2] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y2); quad[3] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y1); break; case BSBottom: quad[0] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y1); quad[1] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y2); quad[2] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y2); quad[3] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y1); break; case BSLeft: quad[0] = FloatPoint(x1, y1 + std::max(-adjacentWidth1, 0)); quad[1] = FloatPoint(x1, y2 - std::max(-adjacentWidth2, 0)); quad[2] = FloatPoint(x2, y2 - std::max(adjacentWidth2, 0)); quad[3] = FloatPoint(x2, y1 + std::max(adjacentWidth1, 0)); break; case BSRight: quad[0] = FloatPoint(x1, y1 + std::max(adjacentWidth1, 0)); quad[1] = FloatPoint(x1, y2 - std::max(adjacentWidth2, 0)); quad[2] = FloatPoint(x2, y2 - std::max(-adjacentWidth2, 0)); quad[3] = FloatPoint(x2, y1 + std::max(-adjacentWidth1, 0)); break; } graphicsContext->drawConvexPolygon(4, quad, antialias); graphicsContext->setStrokeStyle(oldStrokeStyle); } void RenderObject::paintFocusRing(PaintInfo& paintInfo, const LayoutPoint& paintOffset, RenderStyle* style) { Vector focusRingRects; addFocusRingRects(focusRingRects, paintOffset, paintInfo.paintContainer()); ASSERT(style->outlineStyleIsAuto()); paintInfo.context->drawFocusRing(focusRingRects, style->outlineWidth(), style->outlineOffset(), resolveColor(style, CSSPropertyOutlineColor)); } void RenderObject::paintOutline(PaintInfo& paintInfo, const LayoutRect& paintRect) { RenderStyle* styleToUse = style(); if (!styleToUse->hasOutline()) return; LayoutUnit outlineWidth = styleToUse->outlineWidth(); int outlineOffset = styleToUse->outlineOffset(); if (styleToUse->outlineStyleIsAuto()) return; if (styleToUse->outlineStyle() == BNONE) return; IntRect inner = pixelSnappedIntRect(paintRect); inner.inflate(outlineOffset); IntRect outer = pixelSnappedIntRect(inner); outer.inflate(outlineWidth); // FIXME: This prevents outlines from painting inside the object. See bug 12042 if (outer.isEmpty()) return; EBorderStyle outlineStyle = styleToUse->outlineStyle(); Color outlineColor = resolveColor(styleToUse, CSSPropertyOutlineColor); GraphicsContext* graphicsContext = paintInfo.context; bool useTransparencyLayer = outlineColor.hasAlpha(); if (useTransparencyLayer) { if (outlineStyle == SOLID) { Path path; path.addRect(outer); path.addRect(inner); graphicsContext->setFillRule(RULE_EVENODD); graphicsContext->setFillColor(outlineColor); graphicsContext->fillPath(path); return; } graphicsContext->beginTransparencyLayer(static_cast(outlineColor.alpha()) / 255); outlineColor = Color(outlineColor.red(), outlineColor.green(), outlineColor.blue()); } int leftOuter = outer.x(); int leftInner = inner.x(); int rightOuter = outer.maxX(); int rightInner = inner.maxX(); int topOuter = outer.y(); int topInner = inner.y(); int bottomOuter = outer.maxY(); int bottomInner = inner.maxY(); drawLineForBoxSide(graphicsContext, leftOuter, topOuter, leftInner, bottomOuter, BSLeft, outlineColor, outlineStyle, outlineWidth, outlineWidth); drawLineForBoxSide(graphicsContext, leftOuter, topOuter, rightOuter, topInner, BSTop, outlineColor, outlineStyle, outlineWidth, outlineWidth); drawLineForBoxSide(graphicsContext, rightInner, topOuter, rightOuter, bottomOuter, BSRight, outlineColor, outlineStyle, outlineWidth, outlineWidth); drawLineForBoxSide(graphicsContext, leftOuter, bottomInner, rightOuter, bottomOuter, BSBottom, outlineColor, outlineStyle, outlineWidth, outlineWidth); if (useTransparencyLayer) graphicsContext->endLayer(); } void RenderObject::addChildFocusRingRects(Vector& rects, const LayoutPoint& additionalOffset, const RenderBox* paintContainer) const { for (RenderObject* current = slowFirstChild(); current; current = current->nextSibling()) { if (current->isText()) continue; if (current->isBox()) { RenderBox* box = toRenderBox(current); if (box->hasLayer()) { Vector layerFocusRingRects; box->addFocusRingRects(layerFocusRingRects, LayoutPoint(), box); for (size_t i = 0; i < layerFocusRingRects.size(); ++i) { FloatQuad quadInBox = box->localToContainerQuad(FloatRect(layerFocusRingRects[i]), paintContainer); FloatRect rect = quadInBox.boundingBox(); // Floor the location instead of using pixelSnappedIntRect to match the !hasLayer() path. // FIXME: roundedIntSize matches pixelSnappedIntRect in other places of addFocusRingRects // because we always floor the offset. // This assumption is fragile and should be replaced by better solution. rects.append(IntRect(flooredIntPoint(rect.location()), roundedIntSize(rect.size()))); } } else { FloatPoint pos(additionalOffset); pos.move(box->locationOffset()); box->addFocusRingRects(rects, flooredIntPoint(pos), paintContainer); } } else { current->addFocusRingRects(rects, additionalOffset, paintContainer); } } } IntRect RenderObject::absoluteBoundingBoxRect() const { Vector quads; absoluteQuads(quads); size_t n = quads.size(); if (!n) return IntRect(); IntRect result = quads[0].enclosingBoundingBox(); for (size_t i = 1; i < n; ++i) result.unite(quads[i].enclosingBoundingBox()); return result; } void RenderObject::absoluteFocusRingQuads(Vector& quads) { Vector rects; const RenderBox* container = containerForPaintInvalidation(); addFocusRingRects(rects, LayoutPoint(localToContainerPoint(FloatPoint(), container)), container); size_t count = rects.size(); for (size_t i = 0; i < count; ++i) quads.append(container->localToAbsoluteQuad(FloatQuad(rects[i]))); } FloatRect RenderObject::absoluteBoundingBoxRectForRange(const Range* range) { if (!range || !range->startContainer()) return FloatRect(); range->ownerDocument().updateLayout(); Vector quads; range->textQuads(quads); FloatRect result; for (size_t i = 0; i < quads.size(); ++i) result.unite(quads[i].boundingBox()); return result; } void RenderObject::addAbsoluteRectForLayer(LayoutRect& result) { if (hasLayer()) result.unite(absoluteBoundingBoxRect()); for (RenderObject* current = slowFirstChild(); current; current = current->nextSibling()) current->addAbsoluteRectForLayer(result); } void RenderObject::paint(PaintInfo&, const LayoutPoint&, Vector& layers) { } const RenderView* RenderObject::containerForPaintInvalidation() const { return isRooted() ? view() : 0; } const RenderBox* RenderObject::adjustCompositedContainerForSpecialAncestors(const RenderBox* paintInvalidationContainer) const { // FIXME(sky): We shouldn't have any special ancestors and we don't have composited containers if (paintInvalidationContainer) return paintInvalidationContainer; return view(); } void RenderObject::dirtyLinesFromChangedChild(RenderObject*) { } #ifndef NDEBUG void RenderObject::showTreeForThis() const { if (node()) node()->showTreeForThis(); } void RenderObject::showRenderTreeForThis() const { showRenderTree(this, 0); } void RenderObject::showLineTreeForThis() const { if (containingBlock()) containingBlock()->showLineTreeAndMark(0, 0, 0, 0, this); } void RenderObject::showRenderObject() const { showRenderObject(0); } void RenderObject::showRenderObject(int printedCharacters) const { printedCharacters += fprintf(stderr, "%s %p", renderName(), this); if (node()) { if (printedCharacters) for (; printedCharacters < showTreeCharacterOffset; printedCharacters++) fputc(' ', stderr); fputc('\t', stderr); node()->showNode(); } else fputc('\n', stderr); } void RenderObject::showRenderTreeAndMark(const RenderObject* markedObject1, const char* markedLabel1, const RenderObject* markedObject2, const char* markedLabel2, int depth) const { int printedCharacters = 0; if (markedObject1 == this && markedLabel1) printedCharacters += fprintf(stderr, "%s", markedLabel1); if (markedObject2 == this && markedLabel2) printedCharacters += fprintf(stderr, "%s", markedLabel2); for (; printedCharacters < depth * 2; printedCharacters++) fputc(' ', stderr); showRenderObject(printedCharacters); for (const RenderObject* child = slowFirstChild(); child; child = child->nextSibling()) child->showRenderTreeAndMark(markedObject1, markedLabel1, markedObject2, markedLabel2, depth + 1); } #endif // NDEBUG bool RenderObject::isSelectable() const { return !(style()->userSelect() == SELECT_NONE && style()->userModify() == READ_ONLY); } Color RenderObject::selectionBackgroundColor() const { ASSERT_NOT_REACHED(); // TODO(ianh): if we expose selection painting, we should expose a way to set the background colour // TODO(ianh): need to be able to configure whether to consider the selection focused and active or not if (!isSelectable()) return Color::transparent; bool isFocusedAndActive = true; return isFocusedAndActive ? RenderTheme::theme().activeSelectionBackgroundColor() : RenderTheme::theme().inactiveSelectionBackgroundColor(); } Color RenderObject::selectionColor(int colorProperty) const { ASSERT_NOT_REACHED(); // TODO(ianh): if we expose selection painting, we should expose a way to set the text colour // TODO(ianh): need to be able to configure whether to consider the selection focused and active or not if (!isSelectable()) return resolveColor(colorProperty); if (!RenderTheme::theme().supportsSelectionForegroundColors()) return resolveColor(colorProperty); bool isFocusedAndActive = true; return isFocusedAndActive ? RenderTheme::theme().activeSelectionForegroundColor() : RenderTheme::theme().inactiveSelectionForegroundColor(); } Color RenderObject::selectionForegroundColor() const { return selectionColor(CSSPropertyWebkitTextFillColor); } Color RenderObject::selectionEmphasisMarkColor() const { return selectionColor(CSSPropertyWebkitTextEmphasisColor); } void RenderObject::selectionStartEnd(int& spos, int& epos) const { view()->selectionStartEnd(spos, epos); } StyleDifference RenderObject::adjustStyleDifference(StyleDifference diff) const { // The answer to layerTypeRequired() for plugins, iframes, and canvas can change without the actual // style changing, since it depends on whether we decide to composite these elements. When the // layer status of one of these elements changes, we need to force a layout. if (!diff.needsFullLayout() && style() && isBox()) { bool requiresLayer = toRenderBox(this)->layerTypeRequired() != NoLayer; if (hasLayer() != requiresLayer) diff.setNeedsFullLayout(); } return diff; } inline bool RenderObject::hasImmediateNonWhitespaceTextChildOrPropertiesDependentOnColor() const { if (style()->hasBorder() || style()->hasOutline()) return true; for (const RenderObject* r = slowFirstChild(); r; r = r->nextSibling()) { if (r->isText() && !toRenderText(r)->isAllCollapsibleWhitespace()) return true; if (r->style()->hasOutline() || r->style()->hasBorder()) return true; } return false; } void RenderObject::markContainingBlocksForOverflowRecalc() { for (RenderBlock* container = containingBlock(); container && !container->childNeedsOverflowRecalcAfterStyleChange(); container = container->containingBlock()) container->setChildNeedsOverflowRecalcAfterStyleChange(true); } void RenderObject::setNeedsOverflowRecalcAfterStyleChange() { bool neededRecalc = needsOverflowRecalcAfterStyleChange(); setSelfNeedsOverflowRecalcAfterStyleChange(true); if (!neededRecalc) markContainingBlocksForOverflowRecalc(); } void RenderObject::setStyle(PassRefPtr style) { ASSERT(style); StyleDifference diff; if (m_style) diff = m_style->visualInvalidationDiff(*style); diff = adjustStyleDifference(diff); styleWillChange(diff, *style); RefPtr oldStyle = m_style.release(); setStyleInternal(style); updateFillImages(oldStyle ? &oldStyle->backgroundLayers() : 0, m_style->backgroundLayers()); bool doesNotNeedLayout = !m_parent || isText(); styleDidChange(diff, oldStyle.get()); // FIXME: |this| might be destroyed here. This can currently happen for a RenderTextFragment when // its first-letter block gets an update in RenderTextFragment::styleDidChange. For RenderTextFragment(s), // we will safely bail out with the doesNotNeedLayout flag. We might want to broaden this condition // in the future as we move renderer changes out of layout and into style changes. // FIXME(sky): Remove this. if (doesNotNeedLayout) return; // Now that the layer (if any) has been updated, we need to adjust the diff again, // check whether we should layout now, and decide if we need to invalidate paints. StyleDifference updatedDiff = adjustStyleDifference(diff); if (!diff.needsFullLayout()) { if (updatedDiff.needsFullLayout()) setNeedsLayoutAndPrefWidthsRecalc(); else if (updatedDiff.needsPositionedMovementLayout()) setNeedsPositionedMovementLayout(); } if (diff.transformChanged() && !needsLayout()) { if (RenderBlock* container = containingBlock()) container->setNeedsOverflowRecalcAfterStyleChange(); } } void RenderObject::styleWillChange(StyleDifference diff, const RenderStyle& newStyle) { if (m_style) { if (isOutOfFlowPositioned() && (m_style->position() != newStyle.position())) // For changes in positioning styles, we need to conceivably remove ourselves // from the positioned objects list. toRenderBox(this)->removeFloatingOrPositionedChildFromBlockLists(); s_affectsParentBlock = isFloatingOrOutOfFlowPositioned() && !newStyle.hasOutOfFlowPosition() && parent() && (parent()->isRenderParagraph() || parent()->isRenderInline()); // Clearing these bits is required to avoid leaving stale renderers. // FIXME: We shouldn't need that hack if our logic was totally correct. if (diff.needsLayout()) { clearPositionedState(); } } else { s_affectsParentBlock = false; } } void RenderObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) { if (s_affectsParentBlock) { // An object that was floating or positioned became a normal flow object again. We have to make sure the // render tree updates as needed to accommodate the new normal flow object. setInline(style()->isDisplayInlineType()); ASSERT(isInline() == parent()->isRenderParagraph()); } if (!m_parent) return; if (diff.needsFullLayout()) { // If the object already needs layout, then setNeedsLayout won't do // any work. But if the containing block has changed, then we may need // to mark the new containing blocks for layout. The change that can // directly affect the containing block of this object is a change to // the position style. if (needsLayout() && oldStyle->position() != m_style->position()) markContainingBlocksForLayout(); // Ditto. if (needsOverflowRecalcAfterStyleChange() && oldStyle->position() != m_style->position()) markContainingBlocksForOverflowRecalc(); if (diff.needsFullLayout()) setNeedsLayoutAndPrefWidthsRecalc(); } else if (diff.needsPositionedMovementLayout()) setNeedsPositionedMovementLayout(); // Don't check for paint invalidation here; we need to wait until the layer has been // updated by subclasses before we know if we have to invalidate paints (in setStyle()). } void RenderObject::updateFillImages(const FillLayer* oldLayers, const FillLayer& newLayers) { // Optimize the common case if (oldLayers && !oldLayers->next() && !newLayers.next() && (oldLayers->image() == newLayers.image())) return; // Go through the new layers and addClients first, to avoid removing all clients of an image. for (const FillLayer* currNew = &newLayers; currNew; currNew = currNew->next()) { if (currNew->image()) currNew->image()->addClient(this); } for (const FillLayer* currOld = oldLayers; currOld; currOld = currOld->next()) { if (currOld->image()) currOld->image()->removeClient(this); } } void RenderObject::updateImage(StyleImage* oldImage, StyleImage* newImage) { if (oldImage != newImage) { if (oldImage) oldImage->removeClient(this); if (newImage) newImage->addClient(this); } } LayoutRect RenderObject::viewRect() const { return view()->viewRect(); } FloatPoint RenderObject::localToAbsolute(const FloatPoint& localPoint, MapCoordinatesFlags mode) const { TransformState transformState(TransformState::ApplyTransformDirection, localPoint); mapLocalToContainer(0, transformState, mode | ApplyContainerFlip); transformState.flatten(); return transformState.lastPlanarPoint(); } FloatPoint RenderObject::absoluteToLocal(const FloatPoint& containerPoint, MapCoordinatesFlags mode) const { TransformState transformState(TransformState::UnapplyInverseTransformDirection, containerPoint); mapAbsoluteToLocalPoint(mode, transformState); transformState.flatten(); return transformState.lastPlanarPoint(); } FloatQuad RenderObject::absoluteToLocalQuad(const FloatQuad& quad, MapCoordinatesFlags mode) const { TransformState transformState(TransformState::UnapplyInverseTransformDirection, quad.boundingBox().center(), quad); mapAbsoluteToLocalPoint(mode, transformState); transformState.flatten(); return transformState.lastPlanarQuad(); } void RenderObject::mapLocalToContainer(const RenderBox* paintInvalidationContainer, TransformState& transformState, MapCoordinatesFlags mode) const { if (paintInvalidationContainer == this) return; RenderObject* o = parent(); if (!o) return; // FIXME: this should call offsetFromContainer to share code, but I'm not sure it's ever called. if (mode & ApplyContainerFlip && o->isBox()) mode &= ~ApplyContainerFlip; o->mapLocalToContainer(paintInvalidationContainer, transformState, mode); } const RenderObject* RenderObject::pushMappingToContainer(const RenderBox* ancestorToStopAt, RenderGeometryMap& geometryMap) const { ASSERT_UNUSED(ancestorToStopAt, ancestorToStopAt != this); RenderObject* container = parent(); if (!container) return 0; // FIXME(sky): Do we need to make this call? geometryMap.push(this, LayoutSize(), false); return container; } void RenderObject::mapAbsoluteToLocalPoint(MapCoordinatesFlags mode, TransformState& transformState) const { RenderObject* o = parent(); if (o) o->mapAbsoluteToLocalPoint(mode, transformState); } bool RenderObject::shouldUseTransformFromContainer(const RenderObject* containerObject) const { // hasTransform() indicates whether the object has transform, transform-style or perspective. We just care about transform, // so check the layer's transform directly. return (isBox() && toRenderBox(this)->transform()) || (containerObject && containerObject->style()->hasPerspective()); } void RenderObject::getTransformFromContainer(const RenderObject* containerObject, const LayoutSize& offsetInContainer, TransformationMatrix& transform) const { transform.makeIdentity(); transform.translate(offsetInContainer.width().toFloat(), offsetInContainer.height().toFloat()); TransformationMatrix* localTransform = isBox() ? toRenderBox(this)->transform() : 0; if (localTransform) transform.multiply(*localTransform); if (containerObject && containerObject->hasLayer() && containerObject->style()->hasPerspective()) { // Perpsective on the container affects us, so we have to factor it in here. ASSERT(containerObject->hasLayer()); FloatPoint perspectiveOrigin = toRenderBox(containerObject)->perspectiveOrigin(); TransformationMatrix perspectiveMatrix; perspectiveMatrix.applyPerspective(containerObject->style()->perspective()); transform.translateRight3d(-perspectiveOrigin.x(), -perspectiveOrigin.y(), 0); transform = perspectiveMatrix * transform; transform.translateRight3d(perspectiveOrigin.x(), perspectiveOrigin.y(), 0); } } FloatQuad RenderObject::localToContainerQuad(const FloatQuad& localQuad, const RenderBox* paintInvalidationContainer, MapCoordinatesFlags mode) const { // Track the point at the center of the quad's bounding box. As mapLocalToContainer() calls offsetFromContainer(), // it will use that point as the reference point to decide which column's transform to apply in multiple-column blocks. TransformState transformState(TransformState::ApplyTransformDirection, localQuad.boundingBox().center(), localQuad); mapLocalToContainer(paintInvalidationContainer, transformState, mode | ApplyContainerFlip | UseTransforms); transformState.flatten(); return transformState.lastPlanarQuad(); } FloatPoint RenderObject::localToContainerPoint(const FloatPoint& localPoint, const RenderBox* paintInvalidationContainer, MapCoordinatesFlags mode) const { TransformState transformState(TransformState::ApplyTransformDirection, localPoint); mapLocalToContainer(paintInvalidationContainer, transformState, mode | ApplyContainerFlip | UseTransforms); transformState.flatten(); return transformState.lastPlanarPoint(); } LayoutSize RenderObject::offsetFromContainer(const RenderObject* o, const LayoutPoint& point, bool* offsetDependsOnPoint) const { ASSERT(o == container()); LayoutSize offset; if (offsetDependsOnPoint) *offsetDependsOnPoint = false; return offset; } LayoutSize RenderObject::offsetFromAncestorContainer(const RenderObject* container) const { LayoutSize offset; LayoutPoint referencePoint; const RenderObject* currContainer = this; do { const RenderObject* nextContainer = currContainer->container(); ASSERT(nextContainer); // This means we reached the top without finding container. if (!nextContainer) break; ASSERT(!currContainer->hasTransform()); LayoutSize currentOffset = currContainer->offsetFromContainer(nextContainer, referencePoint); offset += currentOffset; referencePoint.move(currentOffset); currContainer = nextContainer; } while (currContainer != container); return offset; } LayoutRect RenderObject::localCaretRect(InlineBox*, int, LayoutUnit* extraWidthToEndOfLine) { if (extraWidthToEndOfLine) *extraWidthToEndOfLine = 0; return LayoutRect(); } bool RenderObject::isRooted() const { const RenderObject* object = this; while (object->parent() && !object->hasLayer()) object = object->parent(); if (object->hasLayer()) return toRenderBox(object)->layer()->root()->isRootLayer(); return false; } RespectImageOrientationEnum RenderObject::shouldRespectImageOrientation() const { return DoNotRespectImageOrientation; } bool RenderObject::hasEntirelyFixedBackground() const { return m_style->hasEntirelyFixedBackground(); } RenderObject* RenderObject::container(const RenderBox* paintInvalidationContainer, bool* paintInvalidationContainerSkipped) const { if (paintInvalidationContainerSkipped) *paintInvalidationContainerSkipped = false; // This method is extremely similar to containingBlock(), but with a few notable // exceptions. // (1) It can be used on orphaned subtrees, i.e., it can be called safely even when // the object is not part of the primary document subtree yet. // (2) For normal flow elements, it just returns the parent. // (3) For absolute positioned elements, it will return a relative positioned inline. // containingBlock() simply skips relpositioned inlines and lets an enclosing block handle // the layout of the positioned object. This does mean that computePositionedLogicalWidth and // computePositionedLogicalHeight have to use container(). RenderObject* o = parent(); if (isText()) return o; EPosition pos = m_style->position(); if (pos == AbsolutePosition) { // We technically just want our containing block, but // we may not have one if we're part of an uninstalled // subtree. We'll climb as high as we can though. while (o) { if (o->style()->position() != StaticPosition) break; if (o->canContainAbsolutePositionObjects()) break; if (paintInvalidationContainerSkipped && o == paintInvalidationContainer) *paintInvalidationContainerSkipped = true; o = o->parent(); } } return o; } bool RenderObject::isSelectionBorder() const { SelectionState st = selectionState(); return st == SelectionStart || st == SelectionEnd || st == SelectionBoth; } inline void RenderObject::clearLayoutRootIfNeeded() const { if (frame()) { if (FrameView* view = frame()->view()) { if (view->layoutRoot() == this) { if (!documentBeingDestroyed()) ASSERT_NOT_REACHED(); // This indicates a failure to layout the child, which is why // the layout root is still set to |this|. Make sure to clear it // since we are getting destroyed. view->clearLayoutSubtreeRoot(); } } } } void RenderObject::willBeDestroyed() { // Destroy any leftover anonymous children. RenderObjectChildList* children = virtualChildren(); if (children) children->destroyLeftoverChildren(); remove(); setAncestorLineBoxDirty(false); clearLayoutRootIfNeeded(); } void RenderObject::insertedIntoTree() { // FIXME: We should ASSERT(isRooted()) here but generated content makes some out-of-order insertion. // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children // and don't have a layer attached to ourselves. RenderLayer* layer = 0; if (slowFirstChild() || hasLayer()) { layer = parent()->enclosingLayer(); addLayers(layer); } if (parent()->isRenderParagraph()) parent()->dirtyLinesFromChangedChild(this); } void RenderObject::willBeRemovedFromTree() { // FIXME: We should ASSERT(isRooted()) but we have some out-of-order removals which would need to be fixed first. // Keep our layer hierarchy updated. if (slowFirstChild() || hasLayer()) removeLayers(parent()->enclosingLayer()); if (isOutOfFlowPositioned() && parent()->isRenderParagraph()) parent()->dirtyLinesFromChangedChild(this); } void RenderObject::destroy() { #if ENABLE(ASSERT) && ENABLE(OILPAN) ASSERT(!m_didCallDestroy); m_didCallDestroy = true; #endif willBeDestroyed(); postDestroy(); } void RenderObject::postDestroy() { // It seems ugly that this is not in willBeDestroyed(). if (m_style) { for (const FillLayer* bgLayer = &m_style->backgroundLayers(); bgLayer; bgLayer = bgLayer->next()) { if (StyleImage* backgroundImage = bgLayer->image()) backgroundImage->removeClient(this); } } delete this; } PositionWithAffinity RenderObject::positionForPoint(const LayoutPoint&) { return createPositionWithAffinity(caretMinOffset(), DOWNSTREAM); } // FIXME(sky): Change the callers to use nodeAtPoint direclty and remove this function. // Or, rename nodeAtPoint to hitTest? bool RenderObject::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset) { return nodeAtPoint(request, result, locationInContainer, accumulatedOffset); } void RenderObject::updateHitTestResult(HitTestResult& result, const LayoutPoint& point) { if (result.innerNode()) return; Node* node = this->node(); if (node) { result.setInnerNode(node); if (!result.innerNonSharedNode()) result.setInnerNonSharedNode(node); result.setLocalPoint(point); } } bool RenderObject::nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation& /*locationInContainer*/, const LayoutPoint& /*accumulatedOffset*/) { return false; } void RenderObject::scheduleRelayout() { if (isRenderView()) { FrameView* view = toRenderView(this)->frameView(); if (view) view->scheduleRelayout(); } else { if (isRooted()) { if (RenderView* renderView = view()) { if (FrameView* frameView = renderView->frameView()) frameView->scheduleRelayoutOfSubtree(this); } } } } void RenderObject::forceLayout() { setSelfNeedsLayout(true); layout(); } // FIXME: Does this do anything different than forceLayout given that we don't walk // the containing block chain. If not, we should change all callers to use forceLayout. void RenderObject::forceChildLayout() { setNormalChildNeedsLayout(true); layout(); } void RenderObject::getTextDecorations(unsigned decorations, AppliedTextDecoration& underline, AppliedTextDecoration& overline, AppliedTextDecoration& linethrough, bool quirksMode, bool firstlineStyle) { RenderObject* curr = this; RenderStyle* styleToUse = 0; unsigned currDecs = TextDecorationNone; Color resultColor; TextDecorationStyle resultStyle; do { styleToUse = curr->style(firstlineStyle); currDecs = styleToUse->textDecoration(); currDecs &= decorations; resultColor = styleToUse->decorationColor(); resultStyle = styleToUse->textDecorationStyle(); // Parameter 'decorations' is cast as an int to enable the bitwise operations below. if (currDecs) { if (currDecs & TextDecorationUnderline) { decorations &= ~TextDecorationUnderline; underline.color = resultColor; underline.style = resultStyle; } if (currDecs & TextDecorationOverline) { decorations &= ~TextDecorationOverline; overline.color = resultColor; overline.style = resultStyle; } if (currDecs & TextDecorationLineThrough) { decorations &= ~TextDecorationLineThrough; linethrough.color = resultColor; linethrough.style = resultStyle; } } curr = curr->parent(); } while (curr && decorations); // If we bailed out, use the element we bailed out at (typically a or element). if (decorations && curr) { styleToUse = curr->style(firstlineStyle); resultColor = styleToUse->decorationColor(); if (decorations & TextDecorationUnderline) { underline.color = resultColor; underline.style = resultStyle; } if (decorations & TextDecorationOverline) { overline.color = resultColor; overline.style = resultStyle; } if (decorations & TextDecorationLineThrough) { linethrough.color = resultColor; linethrough.style = resultStyle; } } } int RenderObject::caretMinOffset() const { return 0; } int RenderObject::caretMaxOffset() const { if (isReplaced()) return node() ? std::max(1U, node()->countChildren()) : 1; return 0; } int RenderObject::previousOffset(int current) const { return current - 1; } int RenderObject::previousOffsetForBackwardDeletion(int current) const { return current - 1; } int RenderObject::nextOffset(int current) const { return current + 1; } // touch-action applies to all elements with both width AND height properties. // According to the CSS Box Model Spec (http://dev.w3.org/csswg/css-box/#the-width-and-height-properties) // width applies to all elements but non-replaced inline elements, table rows, and row groups and // height applies to all elements but non-replaced inline elements, table columns, and column groups. bool RenderObject::supportsTouchAction() const { if (isInline() && !isReplaced()) return false; return true; } Element* RenderObject::offsetParent() const { Node* node = 0; for (RenderObject* ancestor = parent(); ancestor; ancestor = ancestor->parent()) { // Spec: http://www.w3.org/TR/cssom-view/#offset-attributes node = ancestor->node(); if (!node) continue; if (ancestor->isPositioned()) break; } return node && node->isElementNode() ? toElement(node) : 0; } PositionWithAffinity RenderObject::createPositionWithAffinity(int offset, EAffinity affinity) { // If this is a non-anonymous renderer in an editable area, then it's simple. if (Node* node = this->node()) { if (!node->hasEditableStyle()) { // If it can be found, we prefer a visually equivalent position that is editable. Position position = createLegacyEditingPosition(node, offset); Position candidate = position.downstream(CanCrossEditingBoundary); if (candidate.deprecatedNode()->hasEditableStyle()) return PositionWithAffinity(candidate, affinity); candidate = position.upstream(CanCrossEditingBoundary); if (candidate.deprecatedNode()->hasEditableStyle()) return PositionWithAffinity(candidate, affinity); } // FIXME: Eliminate legacy editing positions return PositionWithAffinity(createLegacyEditingPosition(node, offset), affinity); } // We don't want to cross the boundary between editable and non-editable // regions of the document, but that is either impossible or at least // extremely unlikely in any normal case because we stop as soon as we // find a single non-anonymous renderer. // Find a nearby non-anonymous renderer. RenderObject* child = this; while (RenderObject* parent = child->parent()) { // Find non-anonymous content after. for (RenderObject* renderer = child->nextInPreOrder(parent); renderer; renderer = renderer->nextInPreOrder(parent)) { if (Node* node = renderer->node()) return PositionWithAffinity(firstPositionInOrBeforeNode(node), DOWNSTREAM); } // Find non-anonymous content before. for (RenderObject* renderer = child->previousInPreOrder(); renderer; renderer = renderer->previousInPreOrder()) { if (renderer == parent) break; if (Node* node = renderer->node()) return PositionWithAffinity(lastPositionInOrAfterNode(node), DOWNSTREAM); } // Use the parent itself unless it too is anonymous. if (Node* node = parent->node()) return PositionWithAffinity(firstPositionInOrBeforeNode(node), DOWNSTREAM); // Repeat at the next level up. child = parent; } // Everything was anonymous. Give up. return PositionWithAffinity(); } PositionWithAffinity RenderObject::createPositionWithAffinity(const Position& position) { if (position.isNotNull()) return PositionWithAffinity(position); ASSERT(!node()); return createPositionWithAffinity(0, DOWNSTREAM); } bool RenderObject::canUpdateSelectionOnRootLineBoxes() { if (needsLayout()) return false; RenderBlock* containingBlock = this->containingBlock(); return containingBlock ? !containingBlock->needsLayout() : false; } bool RenderObject::nodeAtFloatPoint(const HitTestRequest&, HitTestResult&, const FloatPoint&) { ASSERT_NOT_REACHED(); return false; } bool RenderObject::isAllowedToModifyRenderTreeStructure(Document& document) { return true; } } // namespace blink #ifndef NDEBUG void showTree(const blink::RenderObject* object) { if (object) object->showTreeForThis(); } void showLineTree(const blink::RenderObject* object) { if (object) object->showLineTreeForThis(); } void showRenderTree(const blink::RenderObject* object1) { showRenderTree(object1, 0); } void showRenderTree(const blink::RenderObject* object1, const blink::RenderObject* object2) { if (object1) { const blink::RenderObject* root = object1; while (root->parent()) root = root->parent(); root->showRenderTreeAndMark(object1, "*", object2, "-", 0); } } #endif