/** * This file is part of the html renderer for KDE. * * Copyright (C) 2001-2003 Lars Knoll (knoll@kde.org) * (C) 2001 Antti Koivisto (koivisto@kde.org) * (C) 2000-2003 Dirk Mueller (mueller@kde.org) * (C) 2002-2003 Apple Computer, Inc. * * 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. * */ //#define DEBUG_LAYOUT #include "rendering/render_container.h" #include "rendering/render_table.h" #include "rendering/render_text.h" #include "rendering/render_image.h" #include "rendering/render_canvas.h" #include "rendering/render_generated.h" #include "rendering/render_inline.h" #include "xml/dom_docimpl.h" #include "css/css_valueimpl.h" #include <kdebug.h> #include <assert.h> using namespace khtml; RenderContainer::RenderContainer(DOM::NodeImpl* node) : RenderObject(node) { m_first = 0; m_last = 0; } void RenderContainer::detach() { if (continuation()) continuation()->detach(); // We simulate removeNode calls for all our children // and set parent to 0 to avoid removeNode from being called. // First call removeLayers and removeFromObjectLists since they assume // a valid render-tree for(RenderObject* n = m_first; n; n = n->nextSibling() ) { n->removeLayers(enclosingLayer()); n->removeFromObjectLists(); } RenderObject* next; for(RenderObject* n = m_first; n; n = next ) { n->setParent(0); next = n->nextSibling(); n->detach(); } m_first = 0; m_last = 0; RenderObject::detach(); } void RenderContainer::addChild(RenderObject *newChild, RenderObject *beforeChild) { #ifdef DEBUG_LAYOUT kdDebug( 6040 ) << this << ": " << renderName() << "(RenderObject)::addChild( " << newChild << ": " << newChild->renderName() << ", " << (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; #endif // protect ourselves from deletion setDoNotDelete(true); bool needsTable = false; if(!newChild->isText() && !newChild->isReplaced()) { switch(newChild->style()->display()) { case INLINE: case BLOCK: case LIST_ITEM: case RUN_IN: case COMPACT: case INLINE_BLOCK: case TABLE: case INLINE_TABLE: break; case TABLE_COLUMN: if ( isTableCol() ) break; // nobreak case TABLE_COLUMN_GROUP: case TABLE_CAPTION: case TABLE_ROW_GROUP: case TABLE_HEADER_GROUP: case TABLE_FOOTER_GROUP: //kdDebug( 6040 ) << "adding section" << endl; if ( !isTable() ) needsTable = true; break; case TABLE_ROW: //kdDebug( 6040 ) << "adding row" << endl; if ( !isTableSection() ) needsTable = true; break; case TABLE_CELL: //kdDebug( 6040 ) << "adding cell" << endl; if ( !isTableRow() ) needsTable = true; // 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. if ( isTableCell() && !firstChild() && !newChild->isTableCell() ) needsTable = false; break; case NONE: // RenderHtml and some others can have display:none // KHTMLAssert(false); break; } } if ( needsTable ) { RenderTable *table; RenderObject *last = beforeChild ? beforeChild->previousSibling() : lastChild(); if ( last && last->isTable() && last->isAnonymous() ) { table = static_cast<RenderTable *>(last); } else { //kdDebug( 6040 ) << "creating anonymous table, before=" << beforeChild << endl; table = new (renderArena()) RenderTable(document() /* is anonymous */); RenderStyle *newStyle = new RenderStyle(); newStyle->inheritFrom(style()); newStyle->setDisplay( TABLE ); newStyle->setFlowAroundFloats( true ); table->setParent( this ); // so it finds the arena table->setStyle(newStyle); table->setParent( 0 ); addChild(table, beforeChild); } table->addChild(newChild); } else { // just add it... insertChildNode(newChild, beforeChild); } if (newChild->isText() && newChild->style()->textTransform() == CAPITALIZE) { DOM::DOMStringImpl* textToTransform = static_cast<RenderText*>(newChild)->originalString(); if (textToTransform) static_cast<RenderText*>(newChild)->setText(textToTransform, true); } newChild->attach(); setDoNotDelete(false); } RenderObject* RenderContainer::removeChildNode(RenderObject* oldChild) { KHTMLAssert(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 ( document()->renderer() ) { oldChild->setNeedsLayoutAndMinMaxRecalc(); oldChild->repaint(); // Keep our layer hierarchy updated. oldChild->removeLayers(enclosingLayer()); // remove the child from any special layout lists oldChild->removeFromObjectLists(); // if oldChild is the start or end of the selection, then clear // the selection to avoid problems of invalid pointers // ### This is not the "proper" solution... ideally the selection // ### should be maintained based on DOM Nodes and a Range, which // ### gets adjusted appropriately when nodes are deleted/inserted // ### near etc. But this at least prevents crashes caused when // ### the start or end of the selection is deleted and then // ### accessed when the user next selects something. if (oldChild->isSelectionBorder()) { RenderObject *root = oldChild; while (root->parent()) root = root->parent(); if (root->isCanvas()) { static_cast<RenderCanvas*>(root)->clearSelection(); } } } // remove the child from the render-tree if (oldChild->previousSibling()) oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); if (oldChild->nextSibling()) oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); if (m_first == oldChild) m_first = oldChild->nextSibling(); if (m_last == oldChild) m_last = oldChild->previousSibling(); oldChild->setPreviousSibling(0); oldChild->setNextSibling(0); oldChild->setParent(0); return oldChild; } void RenderContainer::setStyle(RenderStyle* _style) { RenderObject::setStyle(_style); // If we are a pseudo-container we need to restyle the children if (style()->isGenerated()) { // ### we could save this call when the change only affected // non inherited properties RenderStyle *pseudoStyle = new RenderStyle(); pseudoStyle->inheritFrom(style()); pseudoStyle->ref(); RenderObject *child = firstChild(); while (child != 0) { child->setStyle(pseudoStyle); child = child->nextSibling(); } pseudoStyle->deref(); } } void RenderContainer::updatePseudoChildren() { // In CSS2, before/after pseudo-content cannot nest. Check this first. // Remove when CSS 3 Generated Content becomes Candidate Recommendation if (style()->styleType() == RenderStyle::BEFORE || style()->styleType() == RenderStyle::AFTER) return; updatePseudoChild(RenderStyle::BEFORE); updatePseudoChild(RenderStyle::AFTER); // updatePseudoChild(RenderStyle::MARKER, marker()); } void RenderContainer::updatePseudoChild(RenderStyle::PseudoId type) { // The head manages generated content for its continuations if (isInlineContinuation()) return; RenderStyle* pseudo = style()->getPseudoStyle(type); RenderObject* child = pseudoContainer(type); // Whether or not we currently have generated content attached. bool oldContentPresent = child && (child->style()->styleType() == type); // Whether or not we now want generated content. bool newContentWanted = pseudo && pseudo->display() != NONE; // No generated content if (!oldContentPresent && !newContentWanted) return; bool movedContent = (type == RenderStyle::AFTER && isRenderInline() && continuation()); // Whether or not we want the same old content. bool sameOldContent = oldContentPresent && newContentWanted && !movedContent && (child->style()->contentDataEquivalent(pseudo)); // No change in content, update style if( sameOldContent ) { child->setStyle(pseudo); return; } // 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 (oldContentPresent && (!newContentWanted || !sameOldContent)) { // The child needs to be removed. oldContentPresent = false; child->detach(); child = 0; } // If we have no pseudo-style or if the pseudo's display type is NONE, then we // have no generated content and can now return. if (!newContentWanted) return; // Generated content consists of a single container that houses multiple children (specified // by the content property). This pseudo container gets the pseudo style set on it. RenderContainer* pseudoContainer = 0; pseudoContainer = RenderFlow::createFlow(element(), pseudo, renderArena()); pseudoContainer->setIsAnonymous( true ); pseudoContainer->createGeneratedContent(); // Only add the container if it had content if (pseudoContainer->firstChild()) { addPseudoContainer(pseudoContainer); pseudoContainer->close(); } } void RenderContainer::createGeneratedContent() { RenderStyle* pseudo = style(); RenderStyle* style = new RenderStyle(); style->ref(); style->inheritFrom(pseudo); // Now walk our list of generated content and create render objects for every type // we encounter. for (ContentData* contentData = pseudo->contentData(); contentData; contentData = contentData->_nextContent) { if (contentData->_contentType == CONTENT_TEXT) { RenderText* t = new (renderArena()) RenderText( node(), 0); t->setIsAnonymous( true ); t->setStyle(style); t->setText(contentData->contentText()); addChild(t); } else if (contentData->_contentType == CONTENT_OBJECT) { RenderImage* img = new (renderArena()) RenderImage(node()); img->setIsAnonymous( true ); img->setStyle(style); img->setContentObject(contentData->contentObject()); addChild(img); } else if (contentData->_contentType == CONTENT_COUNTER) { // really a counter or just a glyph? EListStyleType type = (EListStyleType)contentData->contentCounter()->listStyle(); RenderObject *t = 0; if (isListStyleCounted(type)) { t = new (renderArena()) RenderCounter( node(), contentData->contentCounter() ); } else { t = new (renderArena()) RenderGlyph( node(), type ); } t->setIsAnonymous( true ); t->setStyle(style); addChild(t); } else if (contentData->_contentType == CONTENT_QUOTE) { RenderQuote* t = new (renderArena()) RenderQuote( node(), contentData->contentQuote() ); t->setIsAnonymous( true ); t->setStyle(style); addChild(t); } } style->deref(); } RenderContainer* RenderContainer::pseudoContainer(RenderStyle::PseudoId type) const { RenderObject *child = 0; switch (type) { case RenderStyle::AFTER: child = lastChild(); break; case RenderStyle::BEFORE: child = firstChild(); break; case RenderStyle::REPLACED: child = lastChild(); if (child && child->style()->styleType() == RenderStyle::AFTER) child = child->previousSibling(); break; default: child = 0; } if (child && child->style()->styleType() == type) { assert(child->isRenderBlock() || child->isRenderInline()); return static_cast<RenderContainer*>(child); } if (type == RenderStyle::AFTER) { // check continuations if (continuation()) return continuation()->pseudoContainer(type); } if (child && child->isAnonymousBlock()) return static_cast<RenderBlock*>(child)->pseudoContainer(type); return 0; } void RenderContainer::addPseudoContainer(RenderObject* child) { RenderStyle::PseudoId type = child->style()->styleType(); switch (type) { case RenderStyle::AFTER: { RenderObject *o = this; while (o->continuation()) o = o->continuation(); // Coalesce inlines if (child->style()->display() == INLINE && o->lastChild() && o->lastChild()->isAnonymousBlock()) { o->lastChild()->addChild(child, 0); } else o->addChild(child, 0); break; } case RenderStyle::BEFORE: // Coalesce inlines if (child->style()->display() == INLINE && firstChild() && firstChild()->isAnonymousBlock()) { firstChild()->addChild(child, firstChild()->firstChild()); } else addChild(child, firstChild()); break; case RenderStyle::REPLACED: addChild(child, pseudoContainer(RenderStyle::AFTER)); break; default: break; } } void RenderContainer::updateReplacedContent() { // Only for normal elements if (!style() || style()->styleType() != RenderStyle::NOPSEUDO) return; // delete old generated content RenderContainer *container = pseudoContainer(RenderStyle::REPLACED); if (container) { container->detach(); } if (style()->useNormalContent()) return; // create generated content RenderStyle* pseudo = style()->getPseudoStyle(RenderStyle::REPLACED); if (!pseudo) { pseudo = new RenderStyle(); pseudo->inheritFrom(style()); pseudo->setStyleType(RenderStyle::REPLACED); } if (pseudo->useNormalContent()) pseudo->setContentData(style()->contentData()); container = RenderFlow::createFlow(node(), pseudo, renderArena()); container->setIsAnonymous( true ); container->createGeneratedContent(); addChild(container, pseudoContainer(RenderStyle::AFTER)); } void RenderContainer::appendChildNode(RenderObject* newChild) { KHTMLAssert(newChild->parent() == 0); newChild->setParent(this); RenderObject* lChild = lastChild(); if(lChild) { newChild->setPreviousSibling(lChild); lChild->setNextSibling(newChild); } else setFirstChild(newChild); setLastChild(newChild); // 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. if (newChild->firstChild() || newChild->layer()) { RenderLayer* layer = enclosingLayer(); newChild->addLayers(layer, newChild); } newChild->setNeedsLayoutAndMinMaxRecalc(); // Goes up the containing block hierarchy. if (!normalChildNeedsLayout()) setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. } void RenderContainer::insertChildNode(RenderObject* child, RenderObject* beforeChild) { if(!beforeChild) { appendChildNode(child); return; } KHTMLAssert(!child->parent()); while ( beforeChild->parent() != this && beforeChild->parent()->isAnonymousBlock() ) beforeChild = beforeChild->parent(); KHTMLAssert(beforeChild->parent() == this); if(beforeChild == firstChild()) setFirstChild(child); RenderObject* prev = beforeChild->previousSibling(); child->setNextSibling(beforeChild); beforeChild->setPreviousSibling(child); if(prev) prev->setNextSibling(child); child->setPreviousSibling(prev); child->setParent(this); // Keep our layer hierarchy updated. RenderLayer* layer = enclosingLayer(); child->addLayers(layer, child); child->setNeedsLayoutAndMinMaxRecalc(); if (!normalChildNeedsLayout()) setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. } void RenderContainer::layout() { KHTMLAssert( needsLayout() ); KHTMLAssert( minMaxKnown() ); const bool pagedMode = canvas()->pagedMode(); RenderObject *child = firstChild(); while( child ) { if (pagedMode) child->setNeedsLayout(true); child->layoutIfNeeded(); if (child->containsPageBreak()) setContainsPageBreak(true); if (child->needsPageClear()) setNeedsPageClear(true); child = child->nextSibling(); } setNeedsLayout(false); } void RenderContainer::removeLeftoverAnonymousBoxes() { // we have to go over all child nodes and remove anonymous boxes, that do _not_ // have inline children to keep the tree flat RenderObject *child = firstChild(); while( child ) { RenderObject *next = child->nextSibling(); if ( child->isRenderBlock() && child->isAnonymousBlock() && !child->continuation() && !child->childrenInline() && !child->isTableCell() && !child->doNotDelete()) { 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 ); if ( child == firstChild() ) m_first = firstAnChild; if ( child == lastChild() ) m_last = lastAnChild; } else { if ( child->previousSibling() ) child->previousSibling()->setNextSibling( child->nextSibling() ); if ( child->nextSibling() ) child->nextSibling()->setPreviousSibling( child->previousSibling() ); if ( child == firstChild() ) m_first = child->nextSibling(); if ( child == lastChild() ) m_last = child->previousSibling(); } child->setParent( 0 ); child->setPreviousSibling( 0 ); child->setNextSibling( 0 ); if ( !child->isText() ) { RenderContainer *c = static_cast<RenderContainer *>(child); c->m_first = 0; c->m_next = 0; } child->detach(); } child = next; } if ( parent() ) parent()->removeLeftoverAnonymousBoxes(); } #undef DEBUG_LAYOUT