/** * 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(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(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 textToTransform = static_cast(newChild)->originalText(); if (textToTransform) static_cast(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

, if this object is the inline continuation of the , 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 , 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(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(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& 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