diff options
Diffstat (limited to 'WebCore/rendering/RenderContainer.cpp')
-rw-r--r-- | WebCore/rendering/RenderContainer.cpp | 655 |
1 files changed, 655 insertions, 0 deletions
diff --git a/WebCore/rendering/RenderContainer.cpp b/WebCore/rendering/RenderContainer.cpp new file mode 100644 index 0000000..e538cbe --- /dev/null +++ b/WebCore/rendering/RenderContainer.cpp @@ -0,0 +1,655 @@ +/** + * 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) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. + * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) + * + * 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 "RenderContainer.h" + +#include "AXObjectCache.h" +#include "Document.h" +#include "RenderCounter.h" +#include "RenderImage.h" +#include "RenderLayer.h" +#include "RenderListItem.h" +#include "RenderTable.h" +#include "RenderTextFragment.h" +#include "RenderView.h" +#include "htmlediting.h" + +namespace WebCore { + +RenderContainer::RenderContainer(Node* node) + : RenderBox(node) + , m_firstChild(0) + , m_lastChild(0) +{ +} + +RenderContainer::~RenderContainer() +{ +} + +void RenderContainer::destroy() +{ + destroyLeftoverChildren(); + RenderBox::destroy(); +} + +void RenderContainer::destroyLeftoverChildren() +{ + while (m_firstChild) { + if (m_firstChild->isListMarker() || (m_firstChild->style()->styleType() == RenderStyle::FIRST_LETTER && !m_firstChild->isText())) + m_firstChild->remove(); // List markers are owned by their enclosing list and so don't get destroyed by this container. Similarly, first letters are destroyed by their remaining text fragment. + else { + // Destroy any anonymous children remaining in the render tree, as well as implicit (shadow) DOM elements like those used in the engine-based text fields. + if (m_firstChild->element()) + m_firstChild->element()->setRenderer(0); + m_firstChild->destroy(); + } + } +} + +bool RenderContainer::canHaveChildren() const +{ + return true; +} + +static void updateListMarkerNumbers(RenderObject* child) +{ + for (RenderObject* r = child; r; r = r->nextSibling()) + if (r->isListItem()) + static_cast<RenderListItem*>(r)->updateValue(); +} + +void RenderContainer::addChild(RenderObject* newChild, RenderObject* beforeChild) +{ + bool needsTable = false; + + if (newChild->isListItem()) + updateListMarkerNumbers(beforeChild ? beforeChild : m_lastChild); + else if (newChild->isTableCol() && newChild->style()->display() == TABLE_COLUMN_GROUP) + needsTable = !isTable(); + else if (newChild->isRenderBlock() && newChild->style()->display() == TABLE_CAPTION) + needsTable = !isTable(); + else if (newChild->isTableSection()) + needsTable = !isTable(); + else if (newChild->isTableRow()) + needsTable = !isTableSection(); + else if (newChild->isTableCell()) { + needsTable = !isTableRow(); + // I'm not 100% sure this is the best way to fix this, but without this + // change we recurse infinitely when trying to render the CSS2 test page: + // http://www.bath.ac.uk/%7Epy8ieh/internet/eviltests/htmlbodyheadrendering2.html. + // See Radar 2925291. + if (needsTable && isTableCell() && !m_firstChild && !newChild->isTableCell()) + needsTable = false; + } + + if (needsTable) { + RenderTable *table; + if(!beforeChild) + beforeChild = m_lastChild; + if(beforeChild && beforeChild->isAnonymous() && beforeChild->isTable()) + table = static_cast<RenderTable*>(beforeChild); + else { + table = new (renderArena()) RenderTable(document() /* is anonymous */); + RenderStyle *newStyle = new (renderArena()) RenderStyle; + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE); + table->setStyle(newStyle); + addChild(table, beforeChild); + } + table->addChild(newChild); + } else { + // just add it... + insertChildNode(newChild, beforeChild); + } + + if (newChild->isText() && newChild->style()->textTransform() == CAPITALIZE) { + RefPtr<StringImpl> textToTransform = static_cast<RenderText*>(newChild)->originalText(); + if (textToTransform) + static_cast<RenderText*>(newChild)->setText(textToTransform.release(), true); + } +} + +RenderObject* RenderContainer::removeChildNode(RenderObject* oldChild, bool fullRemove) +{ + ASSERT(oldChild->parent() == this); + + // So that we'll get the appropriate dirty bit set (either that a normal flow child got yanked or + // that a positioned child got yanked). We also repaint, so that the area exposed when the child + // disappears gets repainted properly. + if (!documentBeingDestroyed() && fullRemove) { + oldChild->setNeedsLayoutAndPrefWidthsRecalc(); + oldChild->repaint(); + } + + // If we have a line box wrapper, delete it. + oldChild->deleteLineBoxWrapper(); + + if (!documentBeingDestroyed() && fullRemove) { + // if we remove visible child from an invisible parent, we don't know the layer visibility any more + RenderLayer* layer = 0; + if (m_style->visibility() != VISIBLE && oldChild->style()->visibility() == VISIBLE && !oldChild->hasLayer()) { + layer = enclosingLayer(); + layer->dirtyVisibleContentStatus(); + } + + // Keep our layer hierarchy updated. + if (oldChild->firstChild() || oldChild->hasLayer()) { + if (!layer) layer = enclosingLayer(); + oldChild->removeLayers(layer); + } + + // renumber ordered lists + if (oldChild->isListItem()) + updateListMarkerNumbers(oldChild->nextSibling()); + + if (oldChild->isPositioned() && childrenInline()) + dirtyLinesFromChangedChild(oldChild); + } + + // If oldChild is the start or end of the selection, then clear the selection to + // avoid problems of invalid pointers. + // FIXME: The SelectionController should be responsible for this when it + // is notified of DOM mutations. + if (!documentBeingDestroyed() && oldChild->isSelectionBorder()) + view()->clearSelection(); + + // remove the child + if (oldChild->previousSibling()) + oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); + if (oldChild->nextSibling()) + oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); + + if (m_firstChild == oldChild) + m_firstChild = oldChild->nextSibling(); + if (m_lastChild == oldChild) + m_lastChild = oldChild->previousSibling(); + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->childrenChanged(this); + + return oldChild; +} + +void RenderContainer::removeChild(RenderObject* oldChild) +{ + // We do this here instead of in removeChildNode, since the only extremely low-level uses of remove/appendChildNode + // cannot affect the positioned object list, and the floating object list is irrelevant (since the list gets cleared on + // layout anyway). + oldChild->removeFromObjectLists(); + + removeChildNode(oldChild); +} + +RenderObject* RenderContainer::beforeAfterContainer(RenderStyle::PseudoId type) +{ + if (type == RenderStyle::BEFORE) { + RenderObject* first = this; + do { + // Skip list markers. + first = first->firstChild(); + while (first && first->isListMarker()) + first = first->nextSibling(); + } while (first && first->isAnonymous() && first->style()->styleType() == RenderStyle::NOPSEUDO); + if (first && first->style()->styleType() != type) + return 0; + return first; + } + if (type == RenderStyle::AFTER) { + RenderObject* last = this; + do { + last = last->lastChild(); + } while (last && last->isAnonymous() && last->style()->styleType() == RenderStyle::NOPSEUDO && !last->isListMarker()); + if (last && last->style()->styleType() != type) + return 0; + return last; + } + + ASSERT_NOT_REACHED(); + return 0; +} + +void RenderContainer::updateBeforeAfterContent(RenderStyle::PseudoId type) +{ + // If this is an anonymous wrapper, then the parent applies its own pseudo-element style to it. + if (parent() && parent()->createsAnonymousWrapper()) + return; + updateBeforeAfterContentForContainer(type, this); +} + +void RenderContainer::updateBeforeAfterContentForContainer(RenderStyle::PseudoId type, RenderContainer* styledObject) +{ + // In CSS2, before/after pseudo-content cannot nest. Check this first. + if (style()->styleType() == RenderStyle::BEFORE || style()->styleType() == RenderStyle::AFTER) + return; + + RenderStyle* pseudoElementStyle = styledObject->getPseudoStyle(type); + RenderObject* child = beforeAfterContainer(type); + + // Whether or not we currently have generated content attached. + bool oldContentPresent = child; + + // Whether or not we now want generated content. + bool newContentWanted = pseudoElementStyle && pseudoElementStyle->display() != NONE; + + // For <q><p/></q>, if this object is the inline continuation of the <q>, we only want to generate + // :after content and not :before content. + if (newContentWanted && type == RenderStyle::BEFORE && isInlineContinuation()) + newContentWanted = false; + + // Similarly, if we're the beginning of a <q>, and there's an inline continuation for our object, + // then we don't generate the :after content. + if (newContentWanted && type == RenderStyle::AFTER && isRenderInline() && continuation()) + newContentWanted = false; + + // If we don't want generated content any longer, or if we have generated content, but it's no longer + // identical to the new content data we want to build render objects for, then we nuke all + // of the old generated content. + if (!newContentWanted || (oldContentPresent && !child->style()->contentDataEquivalent(pseudoElementStyle))) { + // Nuke the child. + if (child && child->style()->styleType() == type) { + oldContentPresent = false; + child->destroy(); + child = (type == RenderStyle::BEFORE) ? m_firstChild : m_lastChild; + } + } + + // If we have no pseudo-element style or if the pseudo-element style's display type is NONE, then we + // have no generated content and can now return. + if (!newContentWanted) + return; + + if (isInlineFlow() && !pseudoElementStyle->isDisplayInlineType() && pseudoElementStyle->floating() == FNONE && + !(pseudoElementStyle->position() == AbsolutePosition || pseudoElementStyle->position() == FixedPosition)) + // According to the CSS2 spec (the end of section 12.1), the only allowed + // display values for the pseudo style are NONE and INLINE for inline flows. + // FIXME: CSS2.1 lifted this restriction, but block display types will crash. + // For now we at least relax the restriction to allow all inline types like inline-block + // and inline-table. + pseudoElementStyle->setDisplay(INLINE); + + if (oldContentPresent) { + if (child && child->style()->styleType() == type) { + // We have generated content present still. We want to walk this content and update our + // style information with the new pseudo-element style. + child->setStyle(pseudoElementStyle); + + // Note that if we ever support additional types of generated content (which should be way off + // in the future), this code will need to be patched. + for (RenderObject* genChild = child->firstChild(); genChild; genChild = genChild->nextSibling()) { + if (genChild->isText()) + // Generated text content is a child whose style also needs to be set to the pseudo-element style. + genChild->setStyle(pseudoElementStyle); + else if (genChild->isImage()) { + // Images get an empty style that inherits from the pseudo. + RenderStyle* style = new (renderArena()) RenderStyle; + style->inheritFrom(pseudoElementStyle); + genChild->setStyle(style); + } else + // Must be a first-letter container. updateFirstLetter() will take care of it. + ASSERT(genChild->style()->styleType() == RenderStyle::FIRST_LETTER); + } + } + return; // We've updated the generated content. That's all we needed to do. + } + + RenderObject* insertBefore = (type == RenderStyle::BEFORE) ? firstChild() : 0; + + // Generated content consists of a single container that houses multiple children (specified + // by the content property). This generated content container gets the pseudo-element style set on it. + RenderObject* generatedContentContainer = 0; + + // Walk our list of generated content and create render objects for each. + for (const ContentData* content = pseudoElementStyle->contentData(); content; content = content->m_next) { + RenderObject* renderer = 0; + switch (content->m_type) { + case CONTENT_NONE: + break; + case CONTENT_TEXT: + renderer = new (renderArena()) RenderTextFragment(document() /* anonymous object */, content->m_content.m_text); + renderer->setStyle(pseudoElementStyle); + break; + case CONTENT_OBJECT: + if (CachedResource* resource = content->m_content.m_object) + if (resource->type() == CachedResource::ImageResource) { + RenderImage* image = new (renderArena()) RenderImage(document()); // anonymous object + RenderStyle* style = new (renderArena()) RenderStyle; + style->inheritFrom(pseudoElementStyle); + image->setStyle(style); + image->setCachedImage(static_cast<CachedImage*>(resource)); + image->setIsAnonymousImage(true); + renderer = image; + } + break; + case CONTENT_COUNTER: + renderer = new (renderArena()) RenderCounter(document(), *content->m_content.m_counter); + renderer->setStyle(pseudoElementStyle); + break; + } + + if (renderer) { + if (!generatedContentContainer) { + // Make a generated box that might be any display type now that we are able to drill down into children + // to find the original content properly. + generatedContentContainer = RenderObject::createObject(document(), pseudoElementStyle); + generatedContentContainer->setStyle(pseudoElementStyle); + } + generatedContentContainer->addChild(renderer); + } + } + + // Add the pseudo after we've installed all our content so that addChild will be able to find the text + // inside the inline for e.g., first-letter styling. + if (generatedContentContainer) + addChild(generatedContentContainer, insertBefore); +} + +bool RenderContainer::isAfterContent(RenderObject* child) const +{ + if (!child) + return false; + if (child->style()->styleType() != RenderStyle::AFTER) + return false; + // Text nodes don't have their own styles, so ignore the style on a text node. + if (child->isText() && !child->isBR()) + return false; + return true; +} + +void RenderContainer::appendChildNode(RenderObject* newChild, bool fullAppend) +{ + ASSERT(newChild->parent() == 0); + ASSERT(!isBlockFlow() || (!newChild->isTableSection() && !newChild->isTableRow() && !newChild->isTableCell())); + + newChild->setParent(this); + RenderObject* lChild = m_lastChild; + + if (lChild) { + newChild->setPreviousSibling(lChild); + lChild->setNextSibling(newChild); + } else + m_firstChild = newChild; + + m_lastChild = newChild; + + if (fullAppend) { + // 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 (newChild->firstChild() || newChild->hasLayer()) { + layer = enclosingLayer(); + newChild->addLayers(layer, newChild); + } + + // if the new child is visible but this object was not, tell the layer it has some visible content + // that needs to be drawn and layer visibility optimization can't be used + if (style()->visibility() != VISIBLE && newChild->style()->visibility() == VISIBLE && !newChild->hasLayer()) { + if (!layer) + layer = enclosingLayer(); + if (layer) + layer->setHasVisibleContent(true); + } + + if (!newChild->isFloatingOrPositioned() && childrenInline()) + dirtyLinesFromChangedChild(newChild); + } + + newChild->setNeedsLayoutAndPrefWidthsRecalc(); // Goes up the containing block hierarchy. + if (!normalChildNeedsLayout()) + setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->childrenChanged(this); +} + +void RenderContainer::insertChildNode(RenderObject* child, RenderObject* beforeChild, bool fullInsert) +{ + if (!beforeChild) { + appendChildNode(child); + return; + } + + ASSERT(!child->parent()); + while (beforeChild->parent() != this && beforeChild->parent()->isAnonymousBlock()) + beforeChild = beforeChild->parent(); + ASSERT(beforeChild->parent() == this); + + ASSERT(!isBlockFlow() || (!child->isTableSection() && !child->isTableRow() && !child->isTableCell())); + + if (beforeChild == m_firstChild) + m_firstChild = child; + + RenderObject* prev = beforeChild->previousSibling(); + child->setNextSibling(beforeChild); + beforeChild->setPreviousSibling(child); + if(prev) prev->setNextSibling(child); + child->setPreviousSibling(prev); + + child->setParent(this); + + if (fullInsert) { + // 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 (child->firstChild() || child->hasLayer()) { + layer = enclosingLayer(); + child->addLayers(layer, child); + } + + // if the new child is visible but this object was not, tell the layer it has some visible content + // that needs to be drawn and layer visibility optimization can't be used + if (style()->visibility() != VISIBLE && child->style()->visibility() == VISIBLE && !child->hasLayer()) { + if (!layer) + layer = enclosingLayer(); + if (layer) + layer->setHasVisibleContent(true); + } + + + if (!child->isFloating() && childrenInline()) + dirtyLinesFromChangedChild(child); + } + + child->setNeedsLayoutAndPrefWidthsRecalc(); + if (!normalChildNeedsLayout()) + setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. + + if (AXObjectCache::accessibilityEnabled()) + document()->axObjectCache()->childrenChanged(this); +} + +void RenderContainer::layout() +{ + ASSERT(needsLayout()); + + view()->pushLayoutState(this, IntSize(m_x, m_y)); + + RenderObject* child = m_firstChild; + while (child) { + child->layoutIfNeeded(); + ASSERT(child->isRenderInline() || !child->needsLayout()); + child = child->nextSibling(); + } + + view()->popLayoutState(); + setNeedsLayout(false); +} + +void RenderContainer::removeLeftoverAnonymousBlock(RenderBlock* child) +{ + ASSERT(child->isAnonymousBlock()); + ASSERT(!child->childrenInline()); + + if (child->continuation()) + return; + + RenderObject* firstAnChild = child->firstChild(); + RenderObject* lastAnChild = child->lastChild(); + if (firstAnChild) { + RenderObject* o = firstAnChild; + while(o) { + o->setParent(this); + o = o->nextSibling(); + } + firstAnChild->setPreviousSibling(child->previousSibling()); + lastAnChild->setNextSibling(child->nextSibling()); + if (child->previousSibling()) + child->previousSibling()->setNextSibling(firstAnChild); + if (child->nextSibling()) + child->nextSibling()->setPreviousSibling(lastAnChild); + } else { + if (child->previousSibling()) + child->previousSibling()->setNextSibling(child->nextSibling()); + if (child->nextSibling()) + child->nextSibling()->setPreviousSibling(child->previousSibling()); + } + if (child == m_firstChild) + m_firstChild = firstAnChild; + if (child == m_lastChild) + m_lastChild = lastAnChild; + child->setParent(0); + child->setPreviousSibling(0); + child->setNextSibling(0); + if (!child->isText()) { + RenderContainer *c = static_cast<RenderContainer*>(child); + c->m_firstChild = 0; + c->m_next = 0; + } + child->destroy(); +} + +VisiblePosition RenderContainer::positionForCoordinates(int x, int y) +{ + // no children...return this render object's element, if there is one, and offset 0 + if (!m_firstChild) + return VisiblePosition(element(), 0, DOWNSTREAM); + + if (isTable() && element()) { + int right = contentWidth() + borderRight() + paddingRight() + borderLeft() + paddingLeft(); + int bottom = contentHeight() + borderTop() + paddingTop() + borderBottom() + paddingBottom(); + + if (x < 0 || x > right || y < 0 || y > bottom) { + if (x <= right / 2) + return VisiblePosition(Position(element(), 0)); + else + return VisiblePosition(Position(element(), maxDeepOffset(element()))); + } + } + + // Pass off to the closest child. + int minDist = INT_MAX; + RenderObject* closestRenderer = 0; + int newX = x; + int newY = y; + if (isTableRow()) { + newX += xPos(); + newY += yPos(); + } + for (RenderObject* renderer = m_firstChild; renderer; renderer = renderer->nextSibling()) { + if (!renderer->firstChild() && !renderer->isInline() && !renderer->isBlockFlow() + || renderer->style()->visibility() != VISIBLE) + continue; + + int top = borderTop() + paddingTop() + (isTableRow() ? 0 : renderer->yPos()); + int bottom = top + renderer->contentHeight(); + int left = borderLeft() + paddingLeft() + (isTableRow() ? 0 : renderer->xPos()); + int right = left + renderer->contentWidth(); + + if (x <= right && x >= left && y <= top && y >= bottom) { + if (renderer->isTableRow()) + return renderer->positionForCoordinates(x + newX - renderer->xPos(), y + newY - renderer->yPos()); + return renderer->positionForCoordinates(x - renderer->xPos(), y - renderer->yPos()); + } + + // Find the distance from (x, y) to the box. Split the space around the box into 8 pieces + // and use a different compare depending on which piece (x, y) is in. + IntPoint cmp; + if (x > right) { + if (y < top) + cmp = IntPoint(right, top); + else if (y > bottom) + cmp = IntPoint(right, bottom); + else + cmp = IntPoint(right, y); + } else if (x < left) { + if (y < top) + cmp = IntPoint(left, top); + else if (y > bottom) + cmp = IntPoint(left, bottom); + else + cmp = IntPoint(left, y); + } else { + if (y < top) + cmp = IntPoint(x, top); + else + cmp = IntPoint(x, bottom); + } + + int x1minusx2 = cmp.x() - x; + int y1minusy2 = cmp.y() - y; + + int dist = x1minusx2 * x1minusx2 + y1minusy2 * y1minusy2; + if (dist < minDist) { + closestRenderer = renderer; + minDist = dist; + } + } + + if (closestRenderer) + return closestRenderer->positionForCoordinates(newX - closestRenderer->xPos(), newY - closestRenderer->yPos()); + + return VisiblePosition(element(), 0, DOWNSTREAM); +} + +void RenderContainer::addLineBoxRects(Vector<IntRect>& rects, unsigned start, unsigned end) +{ + if (!m_firstChild && (isInline() || isAnonymousBlock())) { + int x, y; + absolutePositionForContent(x, y); + absoluteRects(rects, x, y); + return; + } + + if (!m_firstChild) + return; + + unsigned offset = start; + for (RenderObject* child = childAt(start); child && offset < end; child = child->nextSibling(), ++offset) { + if (child->isText() || child->isInline() || child->isAnonymousBlock()) { + int x, y; + child->absolutePositionForContent(x, y); + child->absoluteRects(rects, x, y); + } + } +} + +#undef DEBUG_LAYOUT + +} // namespace WebCore |