summaryrefslogtreecommitdiffstats
path: root/khtml/rendering/render_container.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khtml/rendering/render_container.cpp')
-rw-r--r--khtml/rendering/render_container.cpp597
1 files changed, 597 insertions, 0 deletions
diff --git a/khtml/rendering/render_container.cpp b/khtml/rendering/render_container.cpp
new file mode 100644
index 000000000..69f987477
--- /dev/null
+++ b/khtml/rendering/render_container.cpp
@@ -0,0 +1,597 @@
+/**
+ * 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