<html>
<head>
<style>
body {
color: black;
padding: 0px 0px 0px 0px;
}
.hidden {
visibility: hidden;
}
</style>
<script>
function print(message, color)
{
var paragraph = document.createElement("div");
paragraph.appendChild(document.createTextNode(message));
paragraph.style.fontFamily = "monospace";
if (color)
paragraph.style.color = color;
document.getElementById("console").appendChild(paragraph);
}
function shouldBe(a, b)
{
var evalA = eval(a);
if (evalA == b)
print("PASS: " + a + " should be " + b + " and is.", "green");
else
print("FAIL: " + a + " should be " + b + " but instead is " + evalA + ".", "red");
}
var event;
function parentEventListener(e)
{
print("DOM EVENT AFTER GARBAGE COLLECTION");
gc();
event = e;
shouldBe("event.myCustomProperty", 1);
event = null; // clear JS reference
}
function childEventListener(e)
{
print("DOM EVENT BEFORE GARBAGE COLLECTION");
e.myCustomProperty = 1;
event = e;
shouldBe("event.myCustomProperty", 1);
event = null; // clear JS reference
}
function testEvents()
{
var parent = document.createElement("p");
var child = document.createElement("p");
parent.appendChild(child);
document.body.appendChild(parent);
if (parent.addEventListener) {
child.addEventListener("click", childEventListener, false);
parent.addEventListener("click", parentEventListener, false);
} else {
child.attachEvent("onclick", childEventListener);
parent.attachEvent("onclick", parentEventListener);
}
if (document.createEvent) {
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, true);
child.dispatchEvent(event);
} else {
child.fireEvent("onclick");
}
}
function testDOMImplementation()
{
var impl = document.implementation.createHTMLDocument('').implementation;
gc();
impl.createHTMLDocument(''); // May crash or throw an exception if we collect parent document of impl.
}
function test()
{
if (window.testRunner)
testRunner.dumpAsText();
print("DOM OBJECTS BEFORE GARBAGE COLLECTION:");
generateProperties();
gc();
print("DOM OBJECTS AFTER GARBAGE COLLECTION:");
testPropertiesAgain();
testEvents();
testDOMImplementation();
}
// By default, we expect that custom properties are not allowed.
// If "allow custom" is specified, then custom properties are allowed and survive garbage collection.
// If "allow custom skip" is specified, this means this property is not guaranteed to survive garbage
// collection, so we don't test it at all, since there is a chance it might due to conservative GC algorithm.
// Any uses of "allow custom skip" represent bugs that need to be fixed.
var objectsToTest = [
[ "document.implementation", "allow custom" ], // DOMImplementation
[ "document.fonts", "allow custom" ], // FontFaceSet
[ "document", "allow custom" ],
[ "document.body", "allow custom" ],
[ "document.body.attributes", "allow custom" ], // NamedNodeMap
[ "document.getElementsByTagName('body')", "allow custom" ], // NodeList
[ "document.getElementsByTagName('canvas')[0].getContext('2d')", "allow custom" ], // CanvasRenderingContext2D
[ "document.getElementsByTagName('canvas')[0].getContext('2d').createLinearGradient(0, 0, 0, 0)" ], // CanvasGradient
[ "document.getElementsByTagName('canvas')[0].getContext('2d').createPattern(document.getElementsByTagName('canvas')[0], 'no-repeat')" ], // CanvasPattern
[ "document.getElementsByTagName('select')[0].options", "allow custom" ],
[ "document.body.childNodes", "allow custom" ], // NodeList
[ "document.body.dataset", "allow custom" ], // DatasetDOMStringMap (DOMStringMap)
[ "document.body.classList", "allow custom" ], // ClassList (DOMTokenList)
[ "document.getElementsByTagName('link')[0].relList", "allow custom" ], // ClassList (DOMTokenList)
[ "document.getElementsByTagName('link')[0].sizes", "allow custom" ], // ClassList (DOMTokenList)
[ "document.getElementsByTagName('output')[0].htmlFor", "allow custom" ], // ClassList (DOMTokenList)
[ "document.getElementsByTagName('output')[0].labels", "allow custom" ], // NodeList
[ "document.getElementsByTagName('iframe')[0].sandbox", "allow custom" ], // ClassList (DOMTokenList)
[ "document.all", "allow custom" ],
[ "document.images", "allow custom" ],
[ "document.embeds", "allow custom" ],
[ "document.applets", "allow custom" ],
[ "document.links", "allow custom" ],
[ "document.forms", "allow custom" ],
[ "document.anchors", "allow custom" ],
[ "document.scripts", "allow custom" ],
[ "document.getElementsByTagName('form')[0].elements", "allow custom" ],
[ "document.getElementsByTagName('table')[0].rows", "allow custom" ],
[ "document.getElementsByTagName('table')[0].rows[0].cells", "allow custom" ],
[ "document.getElementsByTagName('table')[0].tBodies", "allow custom" ],
[ "document.getElementsByTagName('table')[0].tBodies[0].rows", "allow custom" ],
[ "document.body.children", "allow custom" ],
[ "document.getElementsByTagName('map')[0].areas", "allow custom" ],
[ "document.body.style", "allow custom" ],
[ "document.styleSheets", "allow custom" ], // StyleSheetList
[ "document.styleSheets[0]", "allow custom" ],
[ "document.styleSheets[0].cssRules", "allow custom"],
[ "document.styleSheets[0].cssRules[0]", "allow custom" ],
[ "document.getElementsByTagName('style')[0].sheet", "allow custom" ],
[ "document.getElementsByTagName('svg')[0].firstChild.sheet", "allow custom" ],
[ "new XPathEvaluator()" ], // XPathEvaluator
[ "new XPathEvaluator().evaluate('/', document, null, 0, null)" ], // XPathResult
[ "document.createExpression('/', document.createNSResolver(document))" ] // XPathExpression
// should not cache: NodeIterator, NodeFilter, TreeWalker, XMLHttpRequest
// add to test: DOMRect, MediaList, Counter, Range
];
function generateProperties()
{
for (var i = 0; i < objectsToTest.length; i++) { // >
try {
eval(objectsToTest[i][0] + ".myCustomProperty = 1;");
} catch(e) {
print("NOT SUPPORTED: " + objectsToTest[i][0] + "[ " + e.message + " ]");
}
var expectedResult = objectsToTest[i][1] ? 1 : undefined;
try {
shouldBe(objectsToTest[i][0] + ".myCustomProperty", expectedResult);
} catch(e) {
}
}
}
function testPropertiesAgain()
{
for (var i = 0; i < objectsToTest.length; i++) { // >
if (objectsToTest[i][1] === "allow custom skip")
continue;
var expectedResult = objectsToTest[i][1] ? 1 : undefined;
try {
shouldBe(objectsToTest[i][0] + ".myCustomProperty", expectedResult);
} catch(e) {
}
}
}
</script>
</head>
<body style="color: black" onload="test();">
<p>This page tests whether custom properties on DOM objects persist after garbage collection.</p>
<p>If the test passes, you'll see a series of 'PASS' messages below.</p>
<p>Because neither WinIE nor FF has reasonable or predictable behavior in this scenario, this
test just documents our behavior to ensure that we don't change it accidentally. It is not
a prescription for how things should behave.</p>
<hr>
<div id='console'></div>
<div class='hidden'>
<canvas></canvas>
<select></select>
<object name="object"></object>
<form></form>
<table><tbody><tr></tr></tbody></table>
<map></map>
<link></link>
<output></output>
<iframe></iframe>
<svg><style></style></svg>
</div>
</body>
</html>