Rework MutationObserver callback.

Previously, MutationObserver callbacks where called using the `jsCallScopeEnd`
mechanism. This was slow and resulted in records split in a way that callers
might not expect. `jsCallScopeEnd` has been removed.

The new approach uses the loop.timeout mechanism, much like a window.setTimeout
and only registers a timeout when events have been handled. It should perform
much better.

Exactly how MutationRecords are supposed to be grouped is still a mystery to me.
This new grouping is still wrong in many cases (according to WPT), but appears
slightly less wrong; I'm pretty hopeful clients don't really have hard-coded
expectations for this though.

Also implement the attributeFilter option of MutationObserver. (Github)
This commit is contained in:
Karl Seguin
2025-07-07 19:29:10 +08:00
parent 300428ddfb
commit e880b18bb1
3 changed files with 96 additions and 69 deletions

View File

@@ -595,9 +595,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
// Some Zig types have code to execute to cleanup
destructor_callbacks: std.ArrayListUnmanaged(DestructorCallback) = .empty,
// Some Zig types have code to execute when the call scope ends
call_scope_end_callbacks: std.ArrayListUnmanaged(CallScopeEndCallback) = .empty,
// Our module cache: normalized module specifier => module.
module_cache: std.StringHashMapUnmanaged(PersistentModule) = .empty,
@@ -828,10 +825,6 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
try self.destructor_callbacks.append(context_arena, DestructorCallback.init(value));
}
if (comptime @hasDecl(ptr.child, "jsCallScopeEnd")) {
try self.call_scope_end_callbacks.append(context_arena, CallScopeEndCallback.init(value));
}
// Sometimes we're creating a new v8.Object, like when
// we're returning a value from a function. In those cases
// we have the FunctionTemplate, and we can get an object
@@ -2639,10 +2632,6 @@ fn Caller(comptime E: type, comptime State: type) type {
// Therefore, we keep a call_depth, and only reset the call_arena
// when a top-level (call_depth == 0) function ends.
if (call_depth == 0) {
for (js_context.call_scope_end_callbacks.items) |cb| {
cb.callScopeEnd();
}
const arena: *ArenaAllocator = @alignCast(@ptrCast(js_context.call_arena.ptr));
_ = arena.reset(.{ .retain_with_limit = CALL_ARENA_RETAIN });
}