Fix for bug 540848 (Add appendChild/insertBefore/replaceChild/removeChild on nsINode). r=bz.

This commit is contained in:
Peter Van der Beken
2010-03-17 16:06:19 +01:00
parent b6eb7c7f60
commit 3dd78141a5
9 changed files with 183 additions and 197 deletions

View File

@@ -567,6 +567,38 @@ nsINode::GetOwnerDocument(nsIDOMDocument** aOwnerDocument)
return ownerDoc ? CallQueryInterface(ownerDoc, aOwnerDocument) : NS_OK;
}
nsresult
nsINode::ReplaceOrInsertBefore(PRBool aReplace, nsIDOMNode* aNewChild,
nsIDOMNode* aRefChild, nsIDOMNode** aReturn)
{
nsCOMPtr<nsINode> newChild = do_QueryInterface(aNewChild);
nsresult rv;
nsCOMPtr<nsINode> refChild;
if (aRefChild) {
refChild = do_QueryInterface(aRefChild, &rv);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = ReplaceOrInsertBefore(aReplace, newChild, refChild);
if (NS_SUCCEEDED(rv)) {
NS_ADDREF(*aReturn = aReplace ? aRefChild : aNewChild);
}
return rv;
}
nsresult
nsINode::RemoveChild(nsIDOMNode* aOldChild, nsIDOMNode** aReturn)
{
nsCOMPtr<nsIContent> oldChild = do_QueryInterface(aOldChild);
nsresult rv = RemoveChild(oldChild);
if (NS_SUCCEEDED(rv)) {
NS_ADDREF(*aReturn = aOldChild);
}
return rv;
}
//----------------------------------------------------------------------
PRInt32
@@ -3516,45 +3548,22 @@ nsGenericElement::SaveSubtreeState()
// Generic DOMNode implementations
/*
* This helper function checks if aChild is the same as aNode or if
* aChild is one of aNode's ancestors. -- jst@citec.fi
*/
NS_IMETHODIMP
nsGenericElement::InsertBefore(nsIDOMNode *aNewChild, nsIDOMNode *aRefChild,
nsIDOMNode **aReturn)
{
return doReplaceOrInsertBefore(PR_FALSE, aNewChild, aRefChild, this, GetCurrentDoc(),
aReturn);
}
NS_IMETHODIMP
nsGenericElement::ReplaceChild(nsIDOMNode* aNewChild, nsIDOMNode* aOldChild,
nsIDOMNode** aReturn)
{
return doReplaceOrInsertBefore(PR_TRUE, aNewChild, aOldChild, this, GetCurrentDoc(),
aReturn);
}
NS_IMETHODIMP
nsGenericElement::RemoveChild(nsIDOMNode *aOldChild, nsIDOMNode **aReturn)
{
return doRemoveChild(aOldChild, this, GetCurrentDoc(),
aReturn);
}
// When replacing, aRefContent is the content being replaced; when
// inserting it's the content before which we're inserting. In the
// latter case it may be null.
static
PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
nsIContent* aParent, nsIDocument* aDocument,
PRBool aIsReplace, nsIContent* aRefContent)
nsINode* aParent, PRBool aIsReplace,
nsIContent* aRefContent)
{
NS_PRECONDITION(aNewChild, "Must have new child");
NS_PRECONDITION(!aIsReplace || aRefContent,
"Must have ref content for replace");
NS_PRECONDITION(aParent->IsNodeOfType(nsINode::eDOCUMENT) ||
aParent->IsNodeOfType(nsINode::eDOCUMENT_FRAGMENT) ||
aParent->IsNodeOfType(nsINode::eELEMENT),
"Nodes that are not documents, document fragments or "
"elements can't be parents!");
#ifdef DEBUG
PRUint16 debugNodeType = 0;
nsCOMPtr<nsIDOMNode> debugNode(do_QueryInterface(aNewChild));
@@ -3581,12 +3590,13 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
return aParent != nsnull;
case nsIDOMNode::ELEMENT_NODE :
{
if (aParent) {
// Always ok to have elements under other elements
if (!aParent->IsNodeOfType(nsINode::eDOCUMENT)) {
// Always ok to have elements under other elements or document fragments
return PR_TRUE;
}
nsIContent* rootContent = aDocument->GetRootContent();
nsIContent* rootContent =
static_cast<nsIDocument*>(aParent)->GetRootContent();
if (rootContent) {
// Already have a documentElement, so this is only OK if we're
// replacing it.
@@ -3601,7 +3611,7 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
}
// Now grovel for a doctype
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aParent);
NS_ASSERTION(doc, "Shouldn't happen");
nsCOMPtr<nsIDOMDocumentType> docType;
doc->GetDoctype(getter_AddRefs(docType));
@@ -3612,8 +3622,8 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
return PR_TRUE;
}
PRInt32 doctypeIndex = aDocument->IndexOf(docTypeContent);
PRInt32 insertIndex = aDocument->IndexOf(aRefContent);
PRInt32 doctypeIndex = aParent->IndexOf(docTypeContent);
PRInt32 insertIndex = aParent->IndexOf(aRefContent);
// Now we're OK in the following two cases only:
// 1) We're replacing something that's not before the doctype
@@ -3623,12 +3633,12 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
}
case nsIDOMNode::DOCUMENT_TYPE_NODE :
{
if (aParent) {
// no doctypes allowed under elements
if (!aParent->IsNodeOfType(nsINode::eDOCUMENT)) {
// doctypes only allowed under documents
return PR_FALSE;
}
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aParent);
NS_ASSERTION(doc, "Shouldn't happen");
nsCOMPtr<nsIDOMDocumentType> docType;
doc->GetDoctype(getter_AddRefs(docType));
@@ -3640,7 +3650,8 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
// We don't have a doctype yet. Our one remaining constraint is
// that the doctype must come before the documentElement.
nsIContent* rootContent = aDocument->GetRootContent();
nsIContent* rootContent =
static_cast<nsIDocument*>(aParent)->GetRootContent();
if (!rootContent) {
// It's all good
return PR_TRUE;
@@ -3651,8 +3662,8 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
return PR_FALSE;
}
PRInt32 rootIndex = aDocument->IndexOf(rootContent);
PRInt32 insertIndex = aDocument->IndexOf(aRefContent);
PRInt32 rootIndex = aParent->IndexOf(rootContent);
PRInt32 insertIndex = aParent->IndexOf(aRefContent);
// Now we're OK if and only if insertIndex <= rootIndex. Indeed, either
// we end up replacing aRefContent or we end up before it. Either one is
@@ -3664,7 +3675,7 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
// Note that for now we only allow nodes inside document fragments if
// they're allowed inside elements. If we ever change this to allow
// doctype nodes in document fragments, we'll need to update this code
if (aParent) {
if (!aParent->IsNodeOfType(nsINode::eDOCUMENT)) {
// All good here
return PR_TRUE;
}
@@ -3686,8 +3697,8 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
nsCOMPtr<nsIDOMNode> childNode(do_QueryInterface(childContent));
PRUint16 type;
childNode->GetNodeType(&type);
if (!IsAllowedAsChild(childContent, type, aParent, aDocument,
aIsReplace, aRefContent)) {
if (!IsAllowedAsChild(childContent, type, aParent, aIsReplace,
aRefContent)) {
return PR_FALSE;
}
}
@@ -3705,116 +3716,102 @@ PRBool IsAllowedAsChild(nsIContent* aNewChild, PRUint16 aNewNodeType,
return PR_FALSE;
}
/* static */
nsresult
nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
nsIDOMNode* aNewChild,
nsIDOMNode* aRefChild,
nsIContent* aParent,
nsIDocument* aDocument,
nsIDOMNode** aReturn)
nsINode::ReplaceOrInsertBefore(PRBool aReplace, nsINode* aNewChild,
nsINode* aRefChild)
{
NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
NS_PRECONDITION(!aParent || aParent->GetCurrentDoc() == aDocument,
"Incorrect aDocument");
*aReturn = nsnull;
if (!aNewChild || (aReplace && !aRefChild)) {
return NS_ERROR_NULL_POINTER;
}
// Keep a strong reference to the node that we'll return to ensure it
// doesn't go away.
nsCOMPtr<nsIDOMNode> returnVal = aReplace ? aRefChild : aNewChild;
nsCOMPtr<nsIContent> refContent;
nsIContent* refContent;
nsresult res = NS_OK;
PRInt32 insPos;
nsINode* container = NODE_FROM(aParent, aDocument);
// Figure out which index to insert at
if (aRefChild) {
refContent = do_QueryInterface(aRefChild);
insPos = container->IndexOf(refContent);
insPos = IndexOf(aRefChild);
if (insPos < 0) {
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
if (aRefChild == aNewChild) {
NS_ADDREF(*aReturn = aNewChild);
return NS_OK;
}
} else {
insPos = container->GetChildCount();
NS_ASSERTION(aRefChild->IsNodeOfType(eCONTENT),
"A child node must be nsIContent!");
refContent = static_cast<nsIContent*>(aRefChild);
}
else {
insPos = GetChildCount();
refContent = nsnull;
}
nsCOMPtr<nsIContent> newContent = do_QueryInterface(aNewChild);
if (!newContent) {
if (!aNewChild->IsNodeOfType(eCONTENT)) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
nsIContent* newContent = static_cast<nsIContent*>(aNewChild);
PRUint16 nodeType = 0;
res = aNewChild->GetNodeType(&nodeType);
NS_ENSURE_SUCCESS(res, res);
// Make sure that the inserted node is allowed as a child of its new parent.
if (!IsAllowedAsChild(newContent, nodeType, aParent, aDocument, aReplace,
refContent)) {
if (!IsAllowedAsChild(newContent, nodeType, this, aReplace, refContent)) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
nsIDocument *doc = container->GetOwnerDoc();
nsIDocument *doc = GetOwnerDoc();
// DocumentType nodes are the only nodes that can have a null
// ownerDocument according to the DOM spec, and we need to allow
// inserting them w/o calling AdoptNode().
if (!container->HasSameOwnerDoc(newContent) &&
if (!HasSameOwnerDoc(newContent) &&
(nodeType != nsIDOMNode::DOCUMENT_TYPE_NODE ||
newContent->GetOwnerDoc())) {
nsCOMPtr<nsIDOM3Document> domDoc = do_QueryInterface(doc);
if (domDoc) {
nsCOMPtr<nsIDOMNode> adoptedKid;
nsresult rv = domDoc->AdoptNode(aNewChild, getter_AddRefs(adoptedKid));
nsresult rv;
nsCOMPtr<nsIDOMNode> newChild = do_QueryInterface(aNewChild, &rv);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(adoptedKid == aNewChild, "Uh, adopt node changed nodes?");
NS_ASSERTION(container->HasSameOwnerDoc(newContent) &&
doc == container->GetOwnerDoc(),
nsCOMPtr<nsIDOMNode> adoptedKid;
rv = domDoc->AdoptNode(newChild, getter_AddRefs(adoptedKid));
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(adoptedKid == newChild, "Uh, adopt node changed nodes?");
NS_ASSERTION(HasSameOwnerDoc(newContent) && doc == GetOwnerDoc(),
"ownerDocument changed again after adopting!");
}
}
// We want an update batch when we expect several mutations to be performed,
// which is when we're replacing a node, or when we're inserting a fragment.
mozAutoDocConditionalContentUpdateBatch batch(aDocument,
mozAutoDocConditionalContentUpdateBatch batch(GetCurrentDoc(),
aReplace || nodeType == nsIDOMNode::DOCUMENT_FRAGMENT_NODE);
// If we're replacing
if (aReplace) {
// Getting (and addrefing) the following child here is sort of wasteful
// in the common case, but really, it's not that expensive. Get over it.
refContent = container->GetChildAt(insPos + 1);
refContent = GetChildAt(insPos + 1);
nsMutationGuard guard;
res = container->RemoveChildAt(insPos, PR_TRUE);
res = RemoveChildAt(insPos, PR_TRUE);
NS_ENSURE_SUCCESS(res, res);
if (guard.Mutated(1)) {
insPos = refContent ? container->IndexOf(refContent) :
container->GetChildCount();
insPos = refContent ? IndexOf(refContent) : GetChildCount();
if (insPos < 0) {
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
// Passing PR_FALSE for aIsReplace since we now have removed the node
// to be replaced.
if (!IsAllowedAsChild(newContent, nodeType, aParent, aDocument,
PR_FALSE, refContent)) {
if (!IsAllowedAsChild(newContent, nodeType, this, PR_FALSE, refContent)) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
@@ -3829,8 +3826,6 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
PRUint32 count = newContent->GetChildCount();
if (!count) {
returnVal.swap(*aReturn);
return NS_OK;
}
@@ -3865,7 +3860,7 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
for (i = 0; i < count; ++i) {
// Get the n:th child from the array.
nsIContent* childContent = fragChildren[i];
if (!container->HasSameOwnerDoc(childContent) ||
if (!HasSameOwnerDoc(childContent) ||
doc != childContent->GetOwnerDoc()) {
return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
}
@@ -3875,14 +3870,13 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
tmpNode->GetNodeType(&tmpType);
if (childContent->GetNodeParent() ||
!IsAllowedAsChild(childContent, tmpType, aParent, aDocument, PR_FALSE,
!IsAllowedAsChild(childContent, tmpType, this, PR_FALSE,
refContent)) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
insPos = refContent ? container->IndexOf(refContent) :
container->GetChildCount();
insPos = refContent ? IndexOf(refContent) : GetChildCount();
if (insPos < 0) {
// Someone seriously messed up the childlist. We have no idea
// where to insert the remaining children, so just bail.
@@ -3890,7 +3884,7 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
}
}
PRBool appending = aParent && (insPos == container->GetChildCount());
PRBool appending = !IsNodeOfType(eDOCUMENT) && insPos == GetChildCount();
PRBool firstInsPos = insPos;
// Iterate through the fragment's children, and insert them in the new
@@ -3900,23 +3894,24 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
// XXXbz how come no reparenting here? That seems odd...
// Insert the child.
res = container->InsertChildAt(childContent, insPos, PR_FALSE);
res = InsertChildAt(childContent, insPos, PR_FALSE);
if (NS_FAILED(res)) {
// Make sure to notify on any children that we did succeed to insert
if (appending && i != 0) {
nsNodeUtils::ContentAppended(aParent, firstInsPos);
nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this),
firstInsPos);
}
return res;
}
if (!appending) {
nsNodeUtils::ContentInserted(container, childContent, insPos);
nsNodeUtils::ContentInserted(this, childContent, insPos);
}
}
// Notify
if (appending) {
nsNodeUtils::ContentAppended(aParent, firstInsPos);
nsNodeUtils::ContentAppended(static_cast<nsIContent*>(this), firstInsPos);
}
// Fire mutation events. Optimize for the case when there are no listeners
@@ -3928,13 +3923,13 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
nsIContent* childContent = fragChildren[i];
if (nsContentUtils::HasMutationListeners(childContent,
NS_EVENT_BITS_MUTATION_NODEINSERTED, container)) {
mozAutoRemovableBlockerRemover blockerRemover(container->GetOwnerDoc());
NS_EVENT_BITS_MUTATION_NODEINSERTED, this)) {
mozAutoRemovableBlockerRemover blockerRemover(doc);
nsMutationEvent mutation(PR_TRUE, NS_MUTATION_NODEINSERTED);
mutation.mRelatedNode = do_QueryInterface(container);
mutation.mRelatedNode = do_QueryInterface(this);
mozAutoSubtreeModified subtree(container->GetOwnerDoc(), container);
mozAutoSubtreeModified subtree(doc, this);
nsEventDispatcher::Dispatch(childContent, nsnull, &mutation);
}
}
@@ -3962,7 +3957,7 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
NS_ASSERTION(!(oldParent == container && removeIndex == insPos),
NS_ASSERTION(!(oldParent == this && removeIndex == insPos),
"invalid removeIndex");
nsMutationGuard guard;
@@ -3972,7 +3967,7 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
// Adjust insert index if the node we ripped out was a sibling
// of the node we're inserting before
if (oldParent == container && removeIndex < insPos) {
if (oldParent == this && removeIndex < insPos) {
--insPos;
}
@@ -3981,8 +3976,7 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
return NS_ERROR_DOM_WRONG_DOCUMENT_ERR;
}
insPos = refContent ? container->IndexOf(refContent) :
container->GetChildCount();
insPos = refContent ? IndexOf(refContent) : GetChildCount();
if (insPos < 0) {
// Someone seriously messed up the childlist. We have no idea
// where to insert the new child, so just bail.
@@ -3990,8 +3984,8 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
}
if (newContent->GetNodeParent() ||
!IsAllowedAsChild(newContent, nodeType, aParent, aDocument,
PR_FALSE, refContent)) {
!IsAllowedAsChild(newContent, nodeType, this, PR_FALSE,
refContent)) {
return NS_ERROR_DOM_HIERARCHY_REQUEST_ERR;
}
}
@@ -4002,45 +3996,13 @@ nsGenericElement::doReplaceOrInsertBefore(PRBool aReplace,
// wrapper is not the wrapper for their ownerDocument (XUL elements,
// form controls, ...).
res = container->InsertChildAt(newContent, insPos, PR_TRUE);
res = InsertChildAt(newContent, insPos, PR_TRUE);
NS_ENSURE_SUCCESS(res, res);
}
returnVal.swap(*aReturn);
return res;
}
/* static */
nsresult
nsGenericElement::doRemoveChild(nsIDOMNode* aOldChild, nsIContent* aParent,
nsIDocument* aDocument, nsIDOMNode** aReturn)
{
NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
NS_PRECONDITION(!aParent || aParent->GetCurrentDoc() == aDocument,
"Incorrect aDocument");
*aReturn = nsnull;
NS_ENSURE_TRUE(aOldChild, NS_ERROR_NULL_POINTER);
nsINode* container = NODE_FROM(aParent, aDocument);
nsCOMPtr<nsIContent> content = do_QueryInterface(aOldChild);
// fix children to be a passed argument
PRInt32 index = container->IndexOf(content);
if (index == -1) {
// aOldChild isn't one of our children.
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
nsresult rv = container->RemoveChildAt(index, PR_TRUE);
*aReturn = aOldChild;
NS_ADDREF(aOldChild);
return rv;
}
//----------------------------------------------------------------------
// nsISupports implementation