Bug 423355: Never fire mutation events on native-anon content. Also includes fix for nsIContent::IsInNativeAnonymousSubtree. Patch by Smaug. r/sr=sicking a=beltzner

This commit is contained in:
jonas@sicking.cc 2008-04-11 15:44:48 -07:00
parent 65ac5b33f5
commit c052f989a1
9 changed files with 204 additions and 42 deletions

View File

@ -62,8 +62,8 @@ class nsIDocShell;
// IID for the nsIContent interface
#define NS_ICONTENT_IID \
{ 0x14c78a19, 0xe3a3, 0x4809, \
{ 0x8a, 0xee, 0x6f, 0x15, 0x4e, 0x1c, 0x16, 0xb6 } }
{ 0x0acd0482, 0x09a2, 0x42fd, \
{ 0xb6, 0x1b, 0x95, 0xa2, 0x01, 0x6a, 0x55, 0xd3 } }
/**
* A node of content in a document's content model. This interface
@ -169,7 +169,25 @@ public:
/**
* Returns PR_TRUE if |this| or any of its ancestors is native anonymous.
*/
virtual PRBool IsInNativeAnonymousSubtree() const;
PRBool IsInNativeAnonymousSubtree() const
{
#ifdef DEBUG
if (HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE)) {
return PR_TRUE;
}
nsIContent* content = GetBindingParent();
while (content) {
if (content->IsNativeAnonymous()) {
NS_ERROR("Element not marked to be in native anonymous subtree!");
break;
}
content = content->GetBindingParent();
}
return PR_FALSE;
#else
return HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE);
#endif
}
/**
* Get the namespace that this element's tag is defined in

View File

@ -76,44 +76,46 @@ enum {
// Whether this node is anonymous
// NOTE: Should only be used on nsIContent nodes
NODE_IS_ANONYMOUS = 0x00000008U,
NODE_IS_IN_ANONYMOUS_SUBTREE = 0x00000010U,
// Whether this node may have a frame
// NOTE: Should only be used on nsIContent nodes
NODE_MAY_HAVE_FRAME = 0x00000010U,
NODE_MAY_HAVE_FRAME = 0x00000020U,
// Forces the XBL code to treat this node as if it were
// in the document and therefore should get bindings attached.
NODE_FORCE_XBL_BINDINGS = 0x00000020U,
NODE_FORCE_XBL_BINDINGS = 0x00000040U,
// Whether a binding manager may have a pointer to this
NODE_MAY_BE_IN_BINDING_MNGR = 0x00000040U,
NODE_MAY_BE_IN_BINDING_MNGR = 0x00000080U,
NODE_IS_EDITABLE = 0x00000080U,
NODE_IS_EDITABLE = 0x00000100U,
// Optimizations to quickly check whether element may have ID, class or style
// attributes. Not all element implementations may use these!
NODE_MAY_HAVE_ID = 0x00000100U,
NODE_MAY_HAVE_CLASS = 0x00000200U,
NODE_MAY_HAVE_STYLE = 0x00000400U,
NODE_MAY_HAVE_ID = 0x00000200U,
NODE_MAY_HAVE_CLASS = 0x00000400U,
NODE_MAY_HAVE_STYLE = 0x00000800U,
NODE_IS_INSERTION_PARENT = 0x00000800U,
NODE_IS_INSERTION_PARENT = 0x00001000U,
// Node has an :empty or :-moz-only-whitespace selector
NODE_HAS_EMPTY_SELECTOR = 0x00001000U,
NODE_HAS_EMPTY_SELECTOR = 0x00002000U,
// A child of the node has a selector such that any insertion,
// removal, or appending of children requires restyling the parent.
NODE_HAS_SLOW_SELECTOR = 0x00002000U,
NODE_HAS_SLOW_SELECTOR = 0x00004000U,
// A child of the node has a :first-child, :-moz-first-node,
// :only-child, :last-child or :-moz-last-node selector.
NODE_HAS_EDGE_CHILD_SELECTOR = 0x00004000U,
NODE_HAS_EDGE_CHILD_SELECTOR = 0x00008000U,
// A child of the node has a selector such that any insertion or
// removal of children requires restyling the parent (but append is
// OK).
NODE_HAS_SLOW_SELECTOR_NOAPPEND
= 0x00008000U,
= 0x00010000U,
NODE_ALL_SELECTOR_FLAGS = NODE_HAS_EMPTY_SELECTOR |
NODE_HAS_SLOW_SELECTOR |
@ -121,7 +123,7 @@ enum {
NODE_HAS_SLOW_SELECTOR_NOAPPEND,
// Four bits for the script-type ID
NODE_SCRIPT_TYPE_OFFSET = 16,
NODE_SCRIPT_TYPE_OFFSET = 17,
NODE_SCRIPT_TYPE_SIZE = 4,

View File

@ -3193,15 +3193,22 @@ nsContentUtils::HasMutationListeners(nsINode* aNode,
return PR_FALSE;
}
doc->MayDispatchMutationEvent(aTargetForSubtreeModified);
// global object will be null for documents that don't have windows.
nsCOMPtr<nsPIDOMWindow> window;
window = do_QueryInterface(doc->GetScriptGlobalObject());
// This relies on nsEventListenerManager::AddEventListener, which sets
// all mutation bits when there is a listener for DOMSubtreeModified event.
if (window && !window->HasMutationListeners(aType)) {
return PR_FALSE;
}
if (aNode->IsNodeOfType(nsINode::eCONTENT) &&
static_cast<nsIContent*>(aNode)->IsInNativeAnonymousSubtree()) {
return PR_FALSE;
}
doc->MayDispatchMutationEvent(aTargetForSubtreeModified);
// If we have a window, we can check it for mutation listeners now.
nsCOMPtr<nsPIDOMEventTarget> piTarget(do_QueryInterface(window));
if (piTarget) {

View File

@ -5860,9 +5860,6 @@ nsDocument::MutationEventDispatched(nsINode* aTarget)
nsINode* possibleTarget = mSubtreeModifiedTargets[i];
nsCOMPtr<nsIContent> content = do_QueryInterface(possibleTarget);
if (content && content->IsInNativeAnonymousSubtree()) {
if (realTargets.IndexOf(possibleTarget) == -1) {
realTargets.AppendObject(possibleTarget);
}
continue;
}

View File

@ -605,7 +605,15 @@ nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsDataSlots *slots = GetDataSlots();
NS_ENSURE_TRUE(slots, NS_ERROR_OUT_OF_MEMORY);
NS_ASSERTION(IsNativeAnonymous() || !HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE) ||
aBindingParent->IsInNativeAnonymousSubtree(),
"Trying to re-bind content from native anonymous subtree to"
"non-native anonymous parent!");
slots->mBindingParent = aBindingParent; // Weak, so no addref happens.
if (IsNativeAnonymous() ||
aBindingParent->IsInNativeAnonymousSubtree()) {
SetFlags(NODE_IS_IN_ANONYMOUS_SUBTREE);
}
}
// Set parent

View File

@ -446,19 +446,6 @@ nsIContent::FindFirstNonNativeAnonymous() const
return possibleResult;
}
PRBool
nsIContent::IsInNativeAnonymousSubtree() const
{
nsIContent* content = GetBindingParent();
while (content) {
if (content->IsNativeAnonymous()) {
return PR_TRUE;
}
content = content->GetBindingParent();
}
return PR_FALSE;
}
//----------------------------------------------------------------------
nsChildContentList::~nsChildContentList()
@ -2069,6 +2056,15 @@ nsGenericElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
slots->mBindingParent = aBindingParent; // Weak, so no addref happens.
}
}
NS_ASSERTION(!aBindingParent || IsNativeAnonymous() ||
!HasFlag(NODE_IS_IN_ANONYMOUS_SUBTREE) ||
aBindingParent->IsInNativeAnonymousSubtree(),
"Trying to re-bind content from native anonymous subtree to"
"non-native anonymous parent!");
if (IsNativeAnonymous() ||
aBindingParent && aBindingParent->IsInNativeAnonymousSubtree()) {
SetFlags(NODE_IS_IN_ANONYMOUS_SUBTREE);
}
PRBool hadForceXBL = HasFlag(NODE_FORCE_XBL_BINDINGS);
@ -2291,13 +2287,11 @@ nsGenericElement::doPreHandleEvent(nsIContent* aContent,
nsCOMPtr<nsIContent> parent = aContent->GetParent();
if (isAnonForEvents) {
// Don't propagate mutation events which are dispatched somewhere inside
// native anonymous content.
if (aVisitor.mEvent->eventStructType == NS_MUTATION_EVENT) {
aVisitor.mParentTarget = nsnull;
return NS_OK;
}
// If a DOM event is explicitly dispatched using node.dispatchEvent(), then
// all the events are allowed even in the native anonymous content..
NS_ASSERTION(aVisitor.mEvent->eventStructType != NS_MUTATION_EVENT ||
aVisitor.mDOMEvent,
"Mutation event dispatched in native anonymous content!?!");
aVisitor.mEventTargetAtParent = parent;
} else if (parent) {
nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->target));

View File

@ -519,7 +519,7 @@ nsEventListenerManager::AddEventListener(nsIDOMEventListener *aListener,
NS_ASSERTION(window->IsInnerWindow(),
"Setting mutation listener bits on outer window?");
// If aType is NS_MUTATION_SUBTREEMODIFIED, we need to listen all
// mutations.
// mutations. nsContentUtils::HasMutationListeners relies on this.
window->SetMutationListeners((aType == NS_MUTATION_SUBTREEMODIFIED) ?
kAllMutationBits :
MutationBitForEventType(aType));

View File

@ -47,6 +47,7 @@ include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
test_bug238987.html \
test_bug288392.html \
test_bug328885.html \
test_bug336682_1.html \
test_bug336682_2.xul \
test_bug336682.js \

View File

@ -0,0 +1,135 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=328885
-->
<head>
<title>Test for Bug 328885</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=328885">Mozilla Bug 328885</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<input type="text" id="inputelement"
style="position: absolute; left: 0px; top: 0px;">
<pre id="test">
<script class="testbody" type="text/javascript">
/** Test for Bug 328885 **/
var inputelement = null;
var mutationCount = 0;
function mutationListener(evt) {
++mutationCount;
}
function clickTest() {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
inputelement.addEventListener("DOMSubtreeModified", mutationListener, false);
inputelement.addEventListener("DOMNodeInserted", mutationListener, false);
inputelement.addEventListener("DOMNodeRemoved", mutationListener, false);
inputelement.addEventListener("DOMNodeRemovedFromDocument", mutationListener, false);
inputelement.addEventListener("DOMNodeInsertedIntoDocument", mutationListener, false);
inputelement.addEventListener("DOMAttrModified", mutationListener, false);
inputelement.addEventListener("DOMCharacterDataModified", mutationListener, false);
inputelement.addEventListener('click',
function(evt) {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
ok(evt.originalTarget instanceof HTMLDivElement, "(1) Wrong originalTarget!");
ok(evt.originalTarget.parentNode == inputelement, "(2) Wront parent node!");
ok(mutationCount == 0, "(3) No mutations should have happened! [" + mutationCount + "]");
evt.originalTarget.textContent = "foo";
ok(mutationCount == 0, "(4) Mutation listener shouldn't have been called! [" + mutationCount + "]");
evt.originalTarget.innerHTML = "foo2";
ok(mutationCount == 0, "(5) Mutation listener shouldn't have been called! [" + mutationCount + "]");
evt.originalTarget.lastChild.data = "bar";
ok(mutationCount == 0, "(6) Mutation listener shouldn't have been called! [" + mutationCount + "]");
var r = document.createRange();
r.selectNodeContents(evt.originalTarget);
r.deleteContents();
ok(mutationCount == 0, "(7) Mutation listener shouldn't have been called! [" + mutationCount + "]");
evt.originalTarget.textContent = "foo";
ok(mutationCount == 0, "(8) Mutation listener shouldn't have been called! [" + mutationCount + "]");
r = document.createRange();
r.selectNodeContents(evt.originalTarget);
r.extractContents();
ok(mutationCount == 0, "(9) Mutation listener shouldn't have been called! [" + mutationCount + "]");
evt.originalTarget.setAttribute("foo", "bar");
ok(mutationCount == 0, "(10) Mutation listener shouldn't have been called! ["+ mutationCount + "]");
// Same tests with non-native-anononymous element.
// mutationCount should be increased by 2 each time, since there is
// first a mutation specific event and then DOMSubtreeModified.
inputelement.textContent = "foo";
ok(mutationCount == 2, "(11) Mutation listener should have been called! [" + mutationCount + "]");
inputelement.lastChild.data = "bar";
ok(mutationCount == 4, "(12) Mutation listener should have been called! [" + mutationCount + "]");
r = document.createRange();
r.selectNodeContents(inputelement);
r.deleteContents();
ok(mutationCount == 6, "(13) Mutation listener should have been called! [" + mutationCount + "]");
inputelement.textContent = "foo";
ok(mutationCount == 8, "(14) Mutation listener should have been called! [" + mutationCount + "]");
r = document.createRange();
r.selectNodeContents(inputelement);
r.extractContents();
ok(mutationCount == 10, "(15) Mutation listener should have been called! [" + mutationCount + "]");
inputelement.setAttribute("foo", "bar");
ok(mutationCount == 12, "(16) Mutation listener should have been called! ["+ mutationCount + "]");
// Then try some mixed mutations. The mutation handler of non-native-a
inputelement.addEventListener("DOMAttrModified",
function (evt2) {
evt.originalTarget.setAttribute("foo", "bar" + mutationCount);
ok(evt.originalTarget.getAttribute("foo") == "bar" + mutationCount,
"(17) Couldn't update the attribute?!?");
}
, false);
inputelement.setAttribute("foo", "");
ok(mutationCount == 14, "(18) Mutation listener should have been called! ["+ mutationCount + "]");
inputelement.textContent = "foo";
ok(mutationCount == 16, "(19) Mutation listener should have been called! ["+ mutationCount + "]");
inputelement.addEventListener("DOMCharacterDataModified",
function (evt2) {
evt.originalTarget.textContent = "bar" + mutationCount;
}, false);
// This one deletes and inserts a new node, then DOMSubtreeModified.
inputelement.textContent = "bar";
ok(mutationCount == 19, "(20) Mutation listener should have been called! ["+ mutationCount + "]");
}
,false);
var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIDOMWindowUtils);
utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0);
utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0);
SimpleTest.finish();
}
function doTest() {
inputelement = document.getElementById('inputelement');
inputelement.focus();
setTimeout(clickTest, 100);
}
SimpleTest.waitForExplicitFinish();
addLoadEvent(doTest);
</script>
</pre>
</body>
</html>