Fix bug 390975: when a form that is not an ancestor of a form control is

removed, clear the control's mForm pointer if it points to the now-removed
form.  r+sr=jst, a=damons


git-svn-id: svn://10.0.0.236/trunk@238905 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
bzbarsky%mit.edu
2007-11-07 17:01:23 +00:00
parent c720fe18ef
commit 3453b63c2a
5 changed files with 158 additions and 6 deletions

View File

@@ -111,12 +111,6 @@
#include "nsLayoutUtils.h"
#include "nsContentCreatorFunctions.h"
// If this flag is set on an nsGenericHTMLFormElement, that means that we have
// added ourselves to our mForm. It's possible to have a non-null mForm, but
// not have this flag set. That happens when the form is set via the content
// sink.
#define ADDED_TO_FORM (1 << NODE_TYPE_SPECIFIC_BITS_OFFSET)
class nsINodeInfo;
class nsIDOMNodeList;
class nsRuleWalker;
@@ -2712,6 +2706,8 @@ nsGenericHTMLFormElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
nsCOMPtr<nsIDOMHTMLFormElement> form = FindForm(mForm);
if (!form) {
SetForm(nsnull, PR_TRUE, PR_TRUE);
} else {
UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
}

View File

@@ -877,6 +877,21 @@ protected:
nsIForm* mForm;
};
// If this flag is set on an nsGenericHTMLFormElement, that means that we have
// added ourselves to our mForm. It's possible to have a non-null mForm, but
// not have this flag set. That happens when the form is set via the content
// sink.
#define ADDED_TO_FORM (1 << NODE_TYPE_SPECIFIC_BITS_OFFSET)
// If this flag is set on an nsGenericHTMLFormElement, that means that its form
// is in the process of being unbound from the tree, and this form element
// hasn't re-found its form in nsGenericHTMLFormElement::UnbindFromTree yet.
#define MAYBE_ORPHAN_FORM_ELEMENT (1 << (NODE_TYPE_SPECIFIC_BITS_OFFSET+1))
// NOTE: I don't think it's possible to have the above two flags set at the
// same time, so if it becomes an issue we can probably merge them into the
// same bit. --bz
//----------------------------------------------------------------------
/**

View File

@@ -711,13 +711,91 @@ nsHTMLFormElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
return rv;
}
static void
MarkOrphans(const nsTArray<nsIFormControl*> aArray)
{
PRUint32 length = aArray.Length();
for (PRUint32 i = 0; i < length; ++i) {
nsCOMPtr<nsINode> node = do_QueryInterface(aArray[i]);
NS_ASSERTION(node, "Form control must be nsINode");
node->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
static void
CollectOrphans(nsINode* aRemovalRoot, nsTArray<nsIFormControl*> aArray
#ifdef DEBUG
, nsIDOMHTMLFormElement* aThisForm
#endif
)
{
// Walk backwards so that if we remove elements we can just keep iterating
PRUint32 length = aArray.Length();
for (PRUint32 i = length; i > 0; --i) {
nsIFormControl* control = aArray[i-1];
nsCOMPtr<nsINode> node = do_QueryInterface(control);
NS_ASSERTION(node, "Form control must be nsINode");
// Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the
// node is in fact a descendant of the form and hence should stay in the
// form. If it _is_ set, then we need to check whether the node is a
// descendant of aRemovalRoot. If it is, we leave it in the form. See
// also the code in nsGenericHTMLFormElement::FindForm.
#ifdef DEBUG
PRBool removed = PR_FALSE;
#endif
if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
if (!nsContentUtils::ContentIsDescendantOf(node, aRemovalRoot)) {
control->SetForm(nsnull, PR_TRUE, PR_TRUE);
#ifdef DEBUG
removed = PR_TRUE;
#endif
}
}
#ifdef DEBUG
if (!removed) {
nsCOMPtr<nsIDOMHTMLFormElement> form;
control->GetForm(getter_AddRefs(form));
NS_ASSERTION(form == aThisForm, "How did that happen?");
}
#endif /* DEBUG */
}
}
void
nsHTMLFormElement::UnbindFromTree(PRBool aDeep, PRBool aNullParent)
{
nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetCurrentDoc());
// Mark all of our controls as maybe being orphans
MarkOrphans(mControls->mElements);
MarkOrphans(mControls->mNotInElements);
nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
nsINode* ancestor = this;
nsINode* cur;
do {
cur = ancestor->GetNodeParent();
if (!cur) {
break;
}
ancestor = cur;
} while (1);
CollectOrphans(ancestor, mControls->mElements
#ifdef DEBUG
, this
#endif
);
CollectOrphans(ancestor, mControls->mNotInElements
#ifdef DEBUG
, this
#endif
);
if (oldDocument) {
oldDocument->RemovedForm();
}

View File

@@ -96,6 +96,7 @@ _TEST_FILES = test_bug589.html \
test_bug386996.html \
test_bug388746.html \
test_bug389797.html \
test_bug390975.html \
test_bug391994.html \
test_bug392567.html \
test_bug394700.html \

View File

@@ -0,0 +1,62 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=390975
-->
<head>
<title>Test for Bug 390975</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=390975">Mozilla Bug 390975</a>
<p id="display"></p>
<div id="content" style="display: none">
<table id="table1">
<form id="form1">
<input>
<input>
<tr><td>
<input>
<input>
<input>
</td></tr>
</form>
</table>
<table id="table2">
<form id="form2">
<input>
<input>
<tr id="row2"><td>
<input>
<input>
<input>
</td></tr>
</form>
</table>
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 390975 **/
var form = $("form1");
is(form.elements.length, 5, "Unexpected elements length");
$("table1").parentNode.removeChild($("table1"));
is(form.elements.length, 3, "Should have lost control outside table");
form.parentNode.removeChild(form);
is(form.elements.length, 0, "Should have lost control outside form");
form = $("form2");
is(form.elements.length, 5, "Unexpected elements length");
$("row2").parentNode.removeChild($("row2"));
is(form.elements.length, 2, "Should have lost controls inside table row");
</script>
</pre>
</body>
</html>