Merge pull request #1107 from lightpanda-io/mutation_observer_improvement
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled

Use correct 'this' on MutationObserver callback
This commit is contained in:
Karl Seguin
2025-10-01 08:44:07 +08:00
committed by GitHub
2 changed files with 75 additions and 18 deletions

View File

@@ -36,8 +36,8 @@ const Walker = @import("../dom/walker.zig").WalkerChildren;
pub const MutationObserver = struct {
page: *Page,
cbk: Env.Function,
connected: bool,
scheduled: bool,
observers: std.ArrayListUnmanaged(*Observer),
// List of records which were observed. When the call scope ends, we need to
// execute our callback with it.
@@ -48,8 +48,8 @@ pub const MutationObserver = struct {
.cbk = cbk,
.page = page,
.observed = .{},
.connected = true,
.scheduled = false,
.observers = .empty,
};
}
@@ -68,15 +68,17 @@ pub const MutationObserver = struct {
.event_node = .{ .id = self.cbk.id, .func = Observer.handle },
};
try self.observers.append(arena, observer);
// register node's events
if (options.childList or options.subtree) {
_ = try parser.eventTargetAddEventListener(
observer.dom_node_inserted_listener = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Node, node),
"DOMNodeInserted",
&observer.event_node,
false,
);
_ = try parser.eventTargetAddEventListener(
observer.dom_node_removed_listener = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Node, node),
"DOMNodeRemoved",
&observer.event_node,
@@ -84,7 +86,7 @@ pub const MutationObserver = struct {
);
}
if (options.attr()) {
_ = try parser.eventTargetAddEventListener(
observer.dom_node_attribute_modified_listener = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Node, node),
"DOMAttrModified",
&observer.event_node,
@@ -92,7 +94,7 @@ pub const MutationObserver = struct {
);
}
if (options.cdata()) {
_ = try parser.eventTargetAddEventListener(
observer.dom_cdata_modified_listener = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Node, node),
"DOMCharacterDataModified",
&observer.event_node,
@@ -100,7 +102,7 @@ pub const MutationObserver = struct {
);
}
if (options.subtree) {
_ = try parser.eventTargetAddEventListener(
observer.dom_subtree_modified_listener = try parser.eventTargetAddEventListener(
parser.toEventTarget(parser.Node, node),
"DOMSubtreeModified",
&observer.event_node,
@@ -111,10 +113,6 @@ pub const MutationObserver = struct {
fn callback(ctx: *anyopaque) ?u32 {
const self: *MutationObserver = @ptrCast(@alignCast(ctx));
if (self.connected == false) {
self.scheduled = true;
return null;
}
self.scheduled = false;
const records = self.observed.items;
@@ -125,7 +123,7 @@ pub const MutationObserver = struct {
defer self.observed.clearRetainingCapacity();
var result: Env.Function.Result = undefined;
self.cbk.tryCall(void, .{records}, &result) catch {
self.cbk.tryCallWithThis(void, self, .{records}, &result) catch {
log.debug(.user_script, "callback error", .{
.err = result.exception,
.stack = result.stack,
@@ -135,9 +133,55 @@ pub const MutationObserver = struct {
return null;
}
// TODO
pub fn _disconnect(self: *MutationObserver) !void {
self.connected = false;
for (self.observers.items) |observer| {
const event_target = parser.toEventTarget(parser.Node, observer.node);
if (observer.dom_node_inserted_listener) |listener| {
try parser.eventTargetRemoveEventListener(
event_target,
"DOMNodeInserted",
listener,
false,
);
}
if (observer.dom_node_removed_listener) |listener| {
try parser.eventTargetRemoveEventListener(
event_target,
"DOMNodeRemoved",
listener,
false,
);
}
if (observer.dom_node_attribute_modified_listener) |listener| {
try parser.eventTargetRemoveEventListener(
event_target,
"DOMAttrModified",
listener,
false,
);
}
if (observer.dom_cdata_modified_listener) |listener| {
try parser.eventTargetRemoveEventListener(
event_target,
"DOMCharacterDataModified",
listener,
false,
);
}
if (observer.dom_subtree_modified_listener) |listener| {
try parser.eventTargetRemoveEventListener(
event_target,
"DOMSubtreeModified",
listener,
false,
);
}
}
self.observers.clearRetainingCapacity();
}
// TODO
@@ -222,6 +266,12 @@ const Observer = struct {
event_node: parser.EventNode,
dom_node_inserted_listener: ?*parser.EventListener = null,
dom_node_removed_listener: ?*parser.EventListener = null,
dom_node_attribute_modified_listener: ?*parser.EventListener = null,
dom_cdata_modified_listener: ?*parser.EventListener = null,
dom_subtree_modified_listener: ?*parser.EventListener = null,
fn appliesTo(
self: *const Observer,
target: *parser.Node,

View File

@@ -11,15 +11,19 @@
var nb = 0;
var mrs;
new MutationObserver((mu) => {
let cb_this1;
let mu1 = new MutationObserver(function(mu) {
mrs = mu;
nb++;
}).observe(document.firstElementChild, { attributes: true, attributeOldValue: true });
cb_this1 = this;
});
mu1.observe(document.firstElementChild, { attributes: true, attributeOldValue: true });
document.firstElementChild.setAttribute("foo", "bar");
document.firstElementChild.firstChild.setAttribute("foo", "bar");
testing.eventually(() => {
testing.expectEqual(1, nb);
testing.expectEqual(cb_this1, mu1);
testing.expectEqual('attributes', mrs[0].type);
testing.expectEqual(document.firstElementChild, mrs[0].target);
testing.expectEqual('bar', mrs[0].target.getAttribute('foo'));
@@ -30,14 +34,17 @@
var nb2 = 0;
var mrs2;
var node1 = $('#p1').firstChild;
new MutationObserver((mu) => {
let mu2 = new MutationObserver((mu) => {
mrs2 = mu;
nb2++;
}).observe(node1, { characterData: true, characterDataOldValue: true });
cb_this2 = this;
})
mu2.observe(node1, { characterData: true, characterDataOldValue: true });
node1.data = "foo";
testing.eventually(() => {
testing.expectEqual(1, nb2);
testing.expectEqual(window, cb_this2);
testing.expectEqual('characterData', mrs2[0].type);
testing.expectEqual(node1, mrs2[0].target);
testing.expectEqual('foo', mrs2[0].target.data);