/** * This file is part of the DOM implementation for KDE. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * Copyright (C) 2004, 2005, 2006 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. */ #include "config.h" #include "ContainerNode.h" #include "DeleteButtonController.h" #include "Document.h" #include "Editor.h" #include "EventNames.h" #include "ExceptionCode.h" #include "FrameView.h" #include "InlineTextBox.h" #include "MutationEvent.h" #include "RenderTheme.h" #include "RootInlineBox.h" #include "SystemTime.h" #include namespace WebCore { using namespace EventNames; static void dispatchChildInsertionEvents(Node*, ExceptionCode&); static void dispatchChildRemovalEvents(Node*, ExceptionCode&); typedef Vector > NodeCallbackQueue; static NodeCallbackQueue* s_postAttachCallbackQueue = 0; static size_t s_attachDepth = 0; ContainerNode::ContainerNode(Document* doc) : EventTargetNode(doc), m_firstChild(0), m_lastChild(0) { } void ContainerNode::removeAllChildren() { // Avoid deep recursion when destroying the node tree. static bool alreadyInsideDestructor; bool topLevel = !alreadyInsideDestructor; if (topLevel) alreadyInsideDestructor = true; // List of nodes to be deleted. static Node* head; static Node* tail; // We have to tell all children that their parent has died. Node* n; Node* next; for (n = m_firstChild; n != 0; n = next) { ASSERT(!n->m_deletionHasBegun); next = n->nextSibling(); n->setPreviousSibling(0); n->setNextSibling(0); n->setParent(0); if (!n->refCount()) { #ifndef NDEBUG n->m_deletionHasBegun = true; #endif // Add the node to the list of nodes to be deleted. // Reuse the nextSibling pointer for this purpose. if (tail) tail->setNextSibling(n); else head = n; tail = n; } else if (n->inDocument()) n->removedFromDocument(); } // Only for the top level call, do the actual deleting. if (topLevel) { while ((n = head) != 0) { ASSERT(n->m_deletionHasBegun); next = n->nextSibling(); n->setNextSibling(0); head = next; if (next == 0) tail = 0; delete n; } alreadyInsideDestructor = false; m_firstChild = 0; m_lastChild = 0; } } ContainerNode::~ContainerNode() { removeAllChildren(); } Node* ContainerNode::virtualFirstChild() const { return m_firstChild; } Node* ContainerNode::virtualLastChild() const { return m_lastChild; } bool ContainerNode::insertBefore(PassRefPtr newChild, Node* refChild, ExceptionCode& ec) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parent()); ec = 0; // insertBefore(node, 0) is equivalent to appendChild(node) if (!refChild) return appendChild(newChild, ec); // Make sure adding the new child is OK. checkAddChild(newChild.get(), ec); if (ec) return false; // NOT_FOUND_ERR: Raised if refChild is not a child of this node if (refChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } bool isFragment = newChild->nodeType() == DOCUMENT_FRAGMENT_NODE; // If newChild is a DocumentFragment with no children; there's nothing to do. // Just return true if (isFragment && !newChild->firstChild()) return true; // Now actually add the child(ren) if (refChild->previousSibling() == newChild || refChild == newChild) // nothing to do return true; RefPtr next = refChild; RefPtr child = isFragment ? newChild->firstChild() : newChild; while (child) { RefPtr nextChild = isFragment ? child->nextSibling() : 0; // If child is already present in the tree, first remove it from the old location. if (Node* oldParent = child->parentNode()) oldParent->removeChild(child.get(), ec); if (ec) return 0; // FIXME: After sending the mutation events, "this" could be destroyed. // We can prevent that by doing a "ref", but first we have to make sure // that no callers call with ref count == 0 and parent = 0 (as of this // writing, there are definitely callers who call that way). // Due to arbitrary code running in response to a DOM mutation event it's // possible that "next" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. if (next->parentNode() != this) break; if (child->parentNode()) break; ASSERT(!child->nextSibling()); ASSERT(!child->previousSibling()); // Add child before "next". forbidEventDispatch(); Node* prev = next->previousSibling(); ASSERT(m_lastChild != prev); next->setPreviousSibling(child.get()); if (prev) { ASSERT(m_firstChild != next); ASSERT(prev->nextSibling() == next); prev->setNextSibling(child.get()); } else { ASSERT(m_firstChild == next); m_firstChild = child.get(); } child->setParent(this); child->setPreviousSibling(prev); child->setNextSibling(next.get()); allowEventDispatch(); // Dispatch the mutation events. dispatchChildInsertionEvents(child.get(), ec); // Add child to the rendering tree. if (attached() && !child->attached() && child->parent() == this) child->attach(); child = nextChild.release(); } document()->setDocumentChanged(true); dispatchSubtreeModifiedEvent(); return true; } bool ContainerNode::replaceChild(PassRefPtr newChild, Node* oldChild, ExceptionCode& ec) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parent()); ec = 0; if (oldChild == newChild) // nothing to do return true; // Make sure replacing the old child with the new is ok checkReplaceChild(newChild.get(), oldChild, ec); if (ec) return false; // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. if (!oldChild || oldChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } RefPtr prev = oldChild->previousSibling(); // Remove the node we're replacing RefPtr removedChild = oldChild; removeChild(oldChild, ec); if (ec) return false; // FIXME: After sending the mutation events, "this" could be destroyed. // We can prevent that by doing a "ref", but first we have to make sure // that no callers call with ref count == 0 and parent = 0 (as of this // writing, there are definitely callers who call that way). bool isFragment = newChild->nodeType() == DOCUMENT_FRAGMENT_NODE; // Add the new child(ren) RefPtr child = isFragment ? newChild->firstChild() : newChild; while (child) { // If the new child is already in the right place, we're done. if (prev && (prev == child || prev == child->previousSibling())) break; // For a fragment we have more children to do. RefPtr nextChild = isFragment ? child->nextSibling() : 0; // Remove child from its old position. if (Node* oldParent = child->parentNode()) oldParent->removeChild(child.get(), ec); if (ec) return 0; // Due to arbitrary code running in response to a DOM mutation event it's // possible that "prev" is no longer a child of "this". // It's also possible that "child" has been inserted elsewhere. // In either of those cases, we'll just stop. if (prev && prev->parentNode() != this) break; if (child->parentNode()) break; ASSERT(!child->nextSibling()); ASSERT(!child->previousSibling()); // Add child after "prev". forbidEventDispatch(); Node* next; if (prev) { next = prev->nextSibling(); ASSERT(m_firstChild != next); prev->setNextSibling(child.get()); } else { next = m_firstChild; m_firstChild = child.get(); } if (next) { ASSERT(m_lastChild != prev); ASSERT(next->previousSibling() == prev); next->setPreviousSibling(child.get()); } else { ASSERT(m_lastChild == prev); m_lastChild = child.get(); } child->setParent(this); child->setPreviousSibling(prev.get()); child->setNextSibling(next); allowEventDispatch(); // Dispatch the mutation events dispatchChildInsertionEvents(child.get(), ec); // Add child to the rendering tree if (attached() && !child->attached() && child->parent() == this) child->attach(); prev = child; child = nextChild.release(); } // ### set style in case it's attached document()->setDocumentChanged(true); dispatchSubtreeModifiedEvent(); return true; } void ContainerNode::willRemove() { for (Node *n = m_firstChild; n != 0; n = n->nextSibling()) n->willRemove(); EventTargetNode::willRemove(); } static ExceptionCode willRemoveChild(Node *child) { ExceptionCode ec = 0; // fire removed from document mutation events. dispatchChildRemovalEvents(child, ec); if (ec) return ec; if (child->attached()) child->willRemove(); return 0; } bool ContainerNode::removeChild(Node* oldChild, ExceptionCode& ec) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parent()); ec = 0; // NO_MODIFICATION_ALLOWED_ERR: Raised if this node is readonly. if (isReadOnlyNode()) { ec = NO_MODIFICATION_ALLOWED_ERR; return false; } // NOT_FOUND_ERR: Raised if oldChild is not a child of this node. if (!oldChild || oldChild->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } RefPtr child = oldChild; // dispatch pre-removal mutation events if (document()->hasListenerType(Document::DOMNODEREMOVED_LISTENER)) { EventTargetNodeCast(child.get())->dispatchEvent(new MutationEvent(DOMNodeRemovedEvent, true, false, this, String(), String(), String(), 0), ec, true); if (ec) return false; } ec = willRemoveChild(child.get()); if (ec) return false; // Mutation events might have moved this child into a different parent. if (child->parentNode() != this) { ec = NOT_FOUND_ERR; return false; } // FIXME: After sending the mutation events, "this" could be destroyed. // We can prevent that by doing a "ref", but first we have to make sure // that no callers call with ref count == 0 and parent = 0 (as of this // writing, there are definitely callers who call that way). forbidEventDispatch(); // Remove from rendering tree if (child->attached()) child->detach(); // Remove the child Node *prev, *next; prev = child->previousSibling(); next = child->nextSibling(); if (next) next->setPreviousSibling(prev); if (prev) prev->setNextSibling(next); if (m_firstChild == child) m_firstChild = next; if (m_lastChild == child) m_lastChild = prev; child->setPreviousSibling(0); child->setNextSibling(0); child->setParent(0); allowEventDispatch(); document()->setDocumentChanged(true); // Dispatch post-removal mutation events dispatchSubtreeModifiedEvent(); if (child->inDocument()) child->removedFromDocument(); else child->removedFromTree(true); return child; } // this differs from other remove functions because it forcibly removes all the children, // regardless of read-only status or event exceptions, e.g. void ContainerNode::removeChildren() { Node *n; if (!m_firstChild) return; // do any prep work needed before actually starting to detach // and remove... e.g. stop loading frames, fire unload events for (n = m_firstChild; n; n = n->nextSibling()) willRemoveChild(n); forbidEventDispatch(); while ((n = m_firstChild) != 0) { Node *next = n->nextSibling(); n->ref(); // Remove the node from the tree before calling detach or removedFromDocument (4427024, 4129744) n->setPreviousSibling(0); n->setNextSibling(0); n->setParent(0); m_firstChild = next; if (n == m_lastChild) m_lastChild = 0; if (n->attached()) n->detach(); if (n->inDocument()) n->removedFromDocument(); n->deref(); } allowEventDispatch(); // Dispatch a single post-removal mutation event denoting a modified subtree. dispatchSubtreeModifiedEvent(); } bool ContainerNode::appendChild(PassRefPtr newChild, ExceptionCode& ec) { // Check that this node is not "floating". // If it is, it can be deleted as a side effect of sending mutation events. ASSERT(refCount() || parent()); ec = 0; // Make sure adding the new child is ok checkAddChild(newChild.get(), ec); if (ec) return 0; if (newChild == m_lastChild) // nothing to do return newChild; bool isFragment = newChild->nodeType() == DOCUMENT_FRAGMENT_NODE; // If newChild is a DocumentFragment with no children.... there's nothing to do. // Just return the document fragment if (isFragment && !newChild->firstChild()) return true; // Now actually add the child(ren) RefPtr child = isFragment ? newChild->firstChild() : newChild; while (child) { // For a fragment we have more children to do. RefPtr nextChild = isFragment ? child->nextSibling() : 0; // If child is already present in the tree, first remove it if (Node* oldParent = child->parentNode()) { oldParent->removeChild(child.get(), ec); if (ec) return 0; // If the child has a parent again, just stop what we're doing, because // that means someone is doing something with DOM mutation -- can't re-parent // a child that already has a parent. if (child->parentNode()) break; } // Append child to the end of the list forbidEventDispatch(); child->setParent(this); if (m_lastChild) { child->setPreviousSibling(m_lastChild); m_lastChild->setNextSibling(child.get()); } else m_firstChild = child.get(); m_lastChild = child.get(); allowEventDispatch(); // Dispatch the mutation events dispatchChildInsertionEvents(child.get(), ec); // Add child to the rendering tree if (attached() && !child->attached() && child->parent() == this) child->attach(); child = nextChild.release(); } document()->setDocumentChanged(true); dispatchSubtreeModifiedEvent(); return true; } bool ContainerNode::hasChildNodes() const { return m_firstChild; } ContainerNode* ContainerNode::addChild(PassRefPtr newChild) { // This function is only used during parsing. // It does not send any DOM mutation events. // Check for consistency with DTD, but only when parsing HTML. if (document()->isHTMLDocument() && !childAllowed(newChild.get())) return 0; forbidEventDispatch(); newChild->setParent(this); if (m_lastChild) { newChild->setPreviousSibling(m_lastChild); m_lastChild->setNextSibling(newChild.get()); } else m_firstChild = newChild.get(); m_lastChild = newChild.get(); allowEventDispatch(); if (inDocument()) newChild->insertedIntoDocument(); if (document()->hasNodeLists()) notifyNodeListsChildrenChanged(); childrenChanged(); if (newChild->isElementNode()) return static_cast(newChild.get()); return this; } void ContainerNode::queuePostAttachCallback(NodeCallback callback, Node* node) { if (!s_postAttachCallbackQueue) s_postAttachCallbackQueue = new NodeCallbackQueue; s_postAttachCallbackQueue->append(std::pair(callback, node)); } void ContainerNode::attach() { ++s_attachDepth; for (Node* child = m_firstChild; child; child = child->nextSibling()) child->attach(); EventTargetNode::attach(); if (s_attachDepth == 1) { if (s_postAttachCallbackQueue) { // We recalculate size() each time through the loop because a callback // can add more callbacks to the end of the queue. for (size_t i = 0; i < s_postAttachCallbackQueue->size(); ++i) { std::pair& pair = (*s_postAttachCallbackQueue)[i]; NodeCallback callback = pair.first; Node* node = pair.second; callback(node); } s_postAttachCallbackQueue->clear(); } } --s_attachDepth; } void ContainerNode::detach() { for (Node* child = m_firstChild; child; child = child->nextSibling()) child->detach(); EventTargetNode::detach(); } void ContainerNode::insertedIntoDocument() { EventTargetNode::insertedIntoDocument(); for (Node *child = m_firstChild; child; child = child->nextSibling()) child->insertedIntoDocument(); } void ContainerNode::removedFromDocument() { EventTargetNode::removedFromDocument(); for (Node *child = m_firstChild; child; child = child->nextSibling()) child->removedFromDocument(); } void ContainerNode::insertedIntoTree(bool deep) { EventTargetNode::insertedIntoTree(deep); if (deep) { for (Node *child = m_firstChild; child; child = child->nextSibling()) child->insertedIntoTree(deep); } } void ContainerNode::removedFromTree(bool deep) { EventTargetNode::removedFromTree(deep); if (deep) { for (Node *child = m_firstChild; child; child = child->nextSibling()) child->removedFromTree(deep); } } void ContainerNode::cloneChildNodes(Node *clone) { // disable the delete button so it's elements are not serialized into the markup if (document()->frame()) document()->frame()->editor()->deleteButtonController()->disable(); ExceptionCode ec = 0; for (Node* n = firstChild(); n && !ec; n = n->nextSibling()) clone->appendChild(n->cloneNode(true), ec); if (document()->frame()) document()->frame()->editor()->deleteButtonController()->enable(); } bool ContainerNode::getUpperLeftCorner(int &xPos, int &yPos) const { if (!renderer()) return false; RenderObject *o = renderer(); RenderObject *p = o; xPos = yPos = 0; if (!o->isInline() || o->isReplaced()) { o->absolutePosition(xPos, yPos); return true; } // find the next text/image child, to get a position while(o) { p = o; if (o->firstChild()) o = o->firstChild(); else if(o->nextSibling()) o = o->nextSibling(); else { RenderObject *next = 0; while (!next && o->parent()) { o = o->parent(); next = o->nextSibling(); } o = next; if (!o) break; } if (!o->isInline() || o->isReplaced()) { o->absolutePosition(xPos, yPos); return true; } if (p->element() && p->element() == this && o->isText() && !o->isBR() && !static_cast(o)->firstTextBox()) { // do nothing - skip unrendered whitespace that is a child or next sibling of the anchor } else if ((o->isText() && !o->isBR()) || o->isReplaced()) { o->container()->absolutePosition(xPos, yPos); if (o->isText() && static_cast(o)->firstTextBox()) { xPos += static_cast(o)->minXPos(); yPos += static_cast(o)->firstTextBox()->root()->topOverflow(); } else { xPos += o->xPos(); yPos += o->yPos(); } return true; } } // If the target doesn't have any children or siblings that could be used to calculate the scroll position, we must be // at the end of the document. Scroll to the bottom. if (!o && document()->view()) { yPos += document()->view()->contentsHeight(); return true; } return false; } bool ContainerNode::getLowerRightCorner(int &xPos, int &yPos) const { if (!renderer()) return false; RenderObject *o = renderer(); xPos = yPos = 0; if (!o->isInline() || o->isReplaced()) { o->absolutePosition(xPos, yPos); xPos += o->width(); yPos += o->height() + o->borderTopExtra() + o->borderBottomExtra(); return true; } // find the last text/image child, to get a position while(o) { if(o->lastChild()) o = o->lastChild(); else if(o->previousSibling()) o = o->previousSibling(); else { RenderObject *prev = 0; while(!prev) { o = o->parent(); if(!o) return false; prev = o->previousSibling(); } o = prev; } if (o->isText() || o->isReplaced()) { o->container()->absolutePosition(xPos, yPos); if (o->isText()) xPos += static_cast(o)->minXPos() + o->width(); else xPos += o->xPos()+o->width(); yPos += o->yPos()+o->height(); return true; } } return true; } IntRect ContainerNode::getRect() const { int xPos = 0, yPos = 0, xEnd = 0, yEnd = 0; bool foundUpperLeft = getUpperLeftCorner(xPos,yPos); bool foundLowerRight = getLowerRightCorner(xEnd,yEnd); // If we've found one corner, but not the other, // then we should just return a point at the corner that we did find. if (foundUpperLeft != foundLowerRight) { if (foundUpperLeft) { xEnd = xPos; yEnd = yPos; } else { xPos = xEnd; yPos = yEnd; } } if (xEnd < xPos) xEnd = xPos; if (yEnd < yPos) yEnd = yPos; return IntRect(xPos, yPos, xEnd - xPos, yEnd - yPos); } void ContainerNode::setFocus(bool received) { if (m_focused == received) return; EventTargetNode::setFocus(received); // note that we need to recalc the style setChanged(); } void ContainerNode::setActive(bool down, bool pause) { if (down == active()) return; EventTargetNode::setActive(down); // note that we need to recalc the style // FIXME: Move to Element if (renderer()) { bool reactsToPress = renderer()->style()->affectedByActiveRules(); if (reactsToPress) setChanged(); if (renderer() && renderer()->style()->hasAppearance()) { if (theme()->stateChanged(renderer(), PressedState)) reactsToPress = true; } if (reactsToPress && pause) { // The delay here is subtle. It relies on an assumption, namely that the amount of time it takes // to repaint the "down" state of the control is about the same time as it would take to repaint the // "up" state. Once you assume this, you can just delay for 100ms - that time (assuming that after you // leave this method, it will be about that long before the flush of the up state happens again). #ifdef HAVE_FUNC_USLEEP double startTime = currentTime(); #endif // Ensure there are no pending changes Document::updateDocumentsRendering(); // Do an immediate repaint. if (renderer()) renderer()->repaint(true); // FIXME: Find a substitute for usleep for Win32. // Better yet, come up with a way of doing this that doesn't use this sort of thing at all. #ifdef HAVE_FUNC_USLEEP // Now pause for a small amount of time (1/10th of a second from before we repainted in the pressed state) double remainingTime = 0.1 - (currentTime() - startTime); if (remainingTime > 0) usleep(static_cast(remainingTime * 1000000.0)); #endif } } } void ContainerNode::setHovered(bool over) { if (over == hovered()) return; EventTargetNode::setHovered(over); // note that we need to recalc the style // FIXME: Move to Element if (renderer()) { if (renderer()->style()->affectedByHoverRules()) setChanged(); if (renderer() && renderer()->style()->hasAppearance()) theme()->stateChanged(renderer(), HoverState); } } unsigned ContainerNode::childNodeCount() const { unsigned count = 0; Node *n; for (n = firstChild(); n; n = n->nextSibling()) count++; return count; } Node *ContainerNode::childNode(unsigned index) const { unsigned i; Node *n = firstChild(); for (i = 0; n != 0 && i < index; i++) n = n->nextSibling(); return n; } static void dispatchChildInsertionEvents(Node* child, ExceptionCode& ec) { ASSERT(!eventDispatchForbidden()); RefPtr c = child; DocPtr doc = child->document(); if (c->parentNode() && c->parentNode()->inDocument()) c->insertedIntoDocument(); else c->insertedIntoTree(true); if (c->parentNode() && doc->hasListenerType(Document::DOMNODEINSERTED_LISTENER) && c->isEventTargetNode()) { ec = 0; EventTargetNodeCast(c.get())->dispatchEvent(new MutationEvent(DOMNodeInsertedEvent, true, false, c->parentNode(), String(), String(), String(), 0), ec, true); if (ec) return; } // dispatch the DOMNodeInsertedIntoDocument event to all descendants if (c->inDocument() && doc->hasListenerType(Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER)) for (; c; c = c->traverseNextNode(child)) { if (!c->isEventTargetNode()) continue; ec = 0; EventTargetNodeCast(c.get())->dispatchEvent(new MutationEvent(DOMNodeInsertedIntoDocumentEvent, false, false, 0, String(), String(), String(), 0), ec, true); if (ec) return; } } static void dispatchChildRemovalEvents(Node* child, ExceptionCode& ec) { RefPtr c = child; DocPtr doc = child->document(); // update auxiliary doc info (e.g. iterators) to note that node is being removed doc->notifyBeforeNodeRemoval(child); // ### use events instead // dispatch pre-removal mutation events if (c->parentNode() && doc->hasListenerType(Document::DOMNODEREMOVED_LISTENER) && c->isEventTargetNode()) { ec = 0; EventTargetNodeCast(c.get())->dispatchEvent(new MutationEvent(DOMNodeRemovedEvent, true, false, c->parentNode(), String(), String(), String(), 0), ec, true); if (ec) return; } // dispatch the DOMNodeRemovedFromDocument event to all descendants if (c->inDocument() && doc->hasListenerType(Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER)) for (; c; c = c->traverseNextNode(child)) { if (!c->isEventTargetNode()) continue; ec = 0; EventTargetNodeCast(c.get())->dispatchEvent(new MutationEvent(DOMNodeRemovedFromDocumentEvent, false, false, 0, String(), String(), String(), 0), ec, true); if (ec) return; } } }