-
-
Notifications
You must be signed in to change notification settings - Fork 247
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Describe the bug
import { Window } from 'happy-dom';
console.log('Testing MutationObserver behavior in detail...');
const window = new Window();
const document = window.document;
let mutationCount = 0;
const observer = new window.MutationObserver((mutations) => {
mutationCount++;
console.log(`\n🔄 Mutation batch #${mutationCount}: ${mutations.length} mutations`);
mutations.forEach((mutation, i) => {
console.log(` ${i+1}. Type: ${mutation.type}`);
console.log(` Target: ${mutation.target.nodeName}`);
console.log(` AttributeName: ${mutation.attributeName}`);
if (mutation.type === 'characterData') {
console.log(` OldValue: "${mutation.oldValue}"`);
console.log(` NewValue: "${mutation.target.textContent}"`);
}
});
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
characterData: true,
attributeOldValue: true,
characterDataOldValue: true
});
// Create element
console.log('\n--- Creating element ---');
const div = document.createElement('div');
div.textContent = 'Initial';
document.body.appendChild(div);
// Wait for async mutations
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
// Test textContent change
console.log('\n--- Changing textContent ---');
div.textContent = 'Changed text';
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
// Test direct text node modification
console.log('\n--- Modifying text node directly ---');
if (div.firstChild && div.firstChild.nodeType === 3) { // Text node
div.firstChild.textContent = 'Direct change';
}
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
// Test setAttribute vs style.setProperty
console.log('\n--- Setting attribute directly ---');
div.setAttribute('style', 'color: red');
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
console.log('\n--- Using style.setProperty ---');
div.style.setProperty('background-color', 'blue');
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
// Test className changes
console.log('\n--- Changing className ---');
div.className = 'test-class';
await new Promise(resolve => setTimeout(resolve, 10));
console.log(`Mutations so far: ${mutationCount}`);
console.log('\nFinal check after all changes...');
observer.disconnect();
To Reproduce
Steps to reproduce the behavior:
Run the script above in bun and node: I see the following in bun:
Testing MutationObserver behavior in detail...
--- Creating element ---
🔄 Mutation batch #1: 1 mutations
1. Type: childList
Target: BODY
AttributeName: null
Mutations so far: 1
--- Changing textContent ---
Mutations so far: 1
--- Modifying text node directly ---
Mutations so far: 1
--- Setting attribute directly ---
Mutations so far: 1
--- Using style.setProperty ---
Mutations so far: 1
--- Changing className ---
Mutations so far: 1
Final check after all changes...
I see the following in node:
Testing MutationObserver behavior in detail...
--- Creating element ---
🔄 Mutation batch #1: 1 mutations
1. Type: childList
Target: BODY
AttributeName: null
Mutations so far: 1
--- Changing textContent ---
🔄 Mutation batch #2: 2 mutations
1. Type: childList
Target: DIV
AttributeName: null
2. Type: childList
Target: DIV
AttributeName: null
Mutations so far: 2
--- Modifying text node directly ---
🔄 Mutation batch #3: 1 mutations
1. Type: characterData
Target: #text
AttributeName: null
OldValue: "Changed text"
NewValue: "Direct change"
Mutations so far: 3
--- Setting attribute directly ---
🔄 Mutation batch #4: 1 mutations
1. Type: attributes
Target: DIV
AttributeName: style
Mutations so far: 4
--- Using style.setProperty ---
🔄 Mutation batch #5: 1 mutations
1. Type: attributes
Target: DIV
AttributeName: style
Mutations so far: 5
--- Changing className ---
🔄 Mutation batch #6: 1 mutations
1. Type: attributes
Target: DIV
AttributeName: class
Mutations so far: 6
Final check after all changes...
Expected behavior
I expect changes to textContent, setAttribute, style.setProperty to cause the MutationObserver to fire in both runtimes. I’ve run the script several times on Bun and seen a situation where textContent causes MutationObserver to fire, so I suspect there’s some deeply f’ed race condition going on. It might have to do with internal garbage collection stuff because your MutationObserver uses WeakRefs.
Device:
- OS: MacOS
- Browser Bun.js
- Version "happy-dom": "^18.0.1",
Additional context
Add any other context about the problem here.
Spixmaster
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working