<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../../../resources/js-test.js"></script>
<title></title>
</head>
<body>
<p id=description></p>
<div id="console"></div>
<script>
window.jsTestIsAsync = true;
var mutations;
var mutations2;
var div;
var subDiv, subDiv2, subDiv3, text;
var calls;
function testBasic() {
var observer;
function start() {
debug('Testing basic aspects of subtree observation.');
mutations = null;
div = document.createElement('div');
subDiv = div.appendChild(document.createElement('div'));
subDiv.innerHTML = 'hello, world';
observer = new MutationObserver(function(mutations) {
window.mutations = mutations;
});
observer.observe(div, {attributes: true, characterData: true, subtree: true});
subDiv.setAttribute('foo', 'bar');
subDiv.firstChild.textContent = 'goodbye!';
setTimeout(finish, 0);
}
function finish() {
debug('...attribute and characterData changes in subtree');
shouldBe('mutations.length', '2');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"foo"');
shouldBe('mutations[0].attributeNamespace', 'null');
shouldBe('mutations[1].type', '"characterData"');
shouldBe('mutations[1].target', 'subDiv.firstChild');
observer.disconnect();
debug('');
runNextTest();
}
start();
}
function testMultipleObservers() {
var observer;
var observer2;
function start() {
debug('Testing two observers at different depths.');
mutations = null;
mutations2 = null;
div = document.createElement('div');
subDiv = div.appendChild(document.createElement('div'));
observer = new MutationObserver(function(mutations) {
window.mutations = mutations;
});
observer2 = new MutationObserver(function(mutations) {
window.mutations2 = mutations;
});
observer.observe(div, {attributes: true, subtree: true});
observer2.observe(subDiv, {attributes: true});
subDiv.setAttribute('foo', 'bar');
setTimeout(finish, 0);
}
function finish() {
shouldBe('mutations.length', '1');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"foo"');
shouldBe('mutations[0].attributeNamespace', 'null');
shouldBe('mutations2.length', '1');
shouldBe('mutations2[0].type', '"attributes"');
shouldBe('mutations2[0].target', 'subDiv');
shouldBe('mutations2[0].attributeName', '"foo"');
shouldBe('mutations2[0].attributeNamespace', 'null');
observer.disconnect();
observer2.disconnect();
debug('');
runNextTest();
}
start();
}
function testMultipleObservations() {
var observer;
function start() {
debug('Testing one observer at two different depths.');
mutations = null;
calls = 0;
div = document.createElement('div');
subDiv = div.appendChild(document.createElement('div'));
observer = new MutationObserver(function(mutations) {
window.mutations = mutations;
++calls;
});
observer.observe(div, {attributes: true, subtree: true});
observer.observe(subDiv, {attributes: true, subtree: true});
subDiv.setAttribute('foo', 'bar');
setTimeout(finish, 0);
}
function finish() {
shouldBe('calls', '1');
shouldBe('mutations.length', '1');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"foo"');
shouldBe('mutations[0].attributeNamespace', 'null');
observer.disconnect();
debug('');
runNextTest();
}
start();
}
function testTransientDetachedBasic() {
var observer;
function start() {
debug('Testing that transiently detached nodes are still observed via subtree.');
mutations = null;
div = document.createElement('div');
subDiv = div.appendChild(document.createElement('div'));
subDiv.innerHTML = 'hello, world';
observer = new MutationObserver(function(mutations) {
window.mutations = mutations;
});
observer.observe(div, {attributes: true, characterData: true, subtree: true});
subDiv.setAttribute('foo', 'bar');
div.removeChild(subDiv);
subDiv.setAttribute('test', 'test');
setTimeout(checkDeliveredAndChangeAgain, 0);
}
function checkDeliveredAndChangeAgain() {
debug('...both changes should be received. Change detached subDiv again.');
shouldBe('mutations.length', '2');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"foo"');
shouldBe('mutations[1].type', '"attributes"');
shouldBe('mutations[1].target', 'subDiv');
shouldBe('mutations[1].attributeName', '"test"');
mutations = null;
subDiv.setAttribute('foo', 'baz');
setTimeout(checkNotDeliveredAndReattach);
}
function checkNotDeliveredAndReattach() {
debug('...transient subtree observation was stopped after delivery, so subDiv change should not be received. Reattach and change again.');
shouldBe('mutations', 'null');
mutations = null
div.appendChild(subDiv);
subDiv.setAttribute('foo', 'bat');
setTimeout(checkDeliveredAndReobserve);
}
function checkDeliveredAndReobserve() {
debug('...reattached subtree should now be observable. Try detaching and re-observing.');
shouldBe('mutations.length', '1');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"foo"');
mutations = null;
div.removeChild(subDiv);
subDiv.firstChild.textContent = 'badbye';
observer.observe(div, {attributes: true, characterData: true, subtree: true});
subDiv.setAttribute('foo', 'boo');
setTimeout(finish);
}
function finish() {
debug('...The change made before re-observing should be received, but not the one after.');
shouldBe('mutations.length', '1');
shouldBe('mutations[0].type', '"characterData"');
shouldBe('mutations[0].target', 'subDiv.firstChild');
observer.disconnect();
debug('');
runNextTest();
}
start();
}
function testTransientDetachedDetailed() {
var observer;
function start() {
debug('Testing correct behavior of transient observation with complex movement .');
mutations = null;
div = document.createElement('div');
subDiv = div.appendChild(document.createElement('div'));
subDiv2 = subDiv.appendChild(document.createElement('div'));
subDiv2.innerHTML = 'hello, world';
subDiv3 = document.createElement('div');
observer = new MutationObserver(function(mutations) {
window.mutations = mutations;
});
observer.observe(div, {attributes: true, characterData: true, subtree: true});
div.removeChild(subDiv);
subDiv.removeChild(subDiv2);
text = subDiv2.removeChild(subDiv2.firstChild);
subDiv.setAttribute('a', 'a');
subDiv2.setAttribute('b', 'b');
text.textContent = 'c';
subDiv3.appendChild(subDiv2);
subDiv3.setAttribute('d', 'd');
subDiv2.setAttribute('e', 'e');
div.appendChild(subDiv3);
subDiv3.setAttribute('f', 'f');
subDiv2.setAttribute('g', 'g');
setTimeout(finish, 0);
}
function finish() {
debug('...All changes should be received except for setting the "d" attribute on subDiv3 before it was reachable from div.');
shouldBe('mutations.length', '6');
shouldBe('mutations[0].type', '"attributes"');
shouldBe('mutations[0].target', 'subDiv');
shouldBe('mutations[0].attributeName', '"a"');
shouldBe('mutations[1].type', '"attributes"');
shouldBe('mutations[1].target', 'subDiv2');
shouldBe('mutations[1].attributeName', '"b"');
shouldBe('mutations[2].type', '"characterData"');
shouldBe('mutations[2].target', 'text');
shouldBe('mutations[3].type', '"attributes"');
shouldBe('mutations[3].target', 'subDiv2');
shouldBe('mutations[3].attributeName', '"e"');
shouldBe('mutations[4].type', '"attributes"');
shouldBe('mutations[4].target', 'subDiv3');
shouldBe('mutations[4].attributeName', '"f"');
shouldBe('mutations[5].type', '"attributes"');
shouldBe('mutations[5].target', 'subDiv2');
shouldBe('mutations[5].attributeName', '"g"');
observer.disconnect();
debug('');
runNextTest();
}
start();
}
var tests = [testBasic, testMultipleObservers, testMultipleObservations, testTransientDetachedBasic, testTransientDetachedDetailed];
var testIndex = 0;
function runNextTest() {
if (testIndex < tests.length)
tests[testIndex++]();
else
finishJSTest();
}
description('Test WebKitMutationObserver.observe on a subtree');
runNextTest();
</script>
</body>
</html>