@@ -22,6 +22,7 @@ const parser = @import("netsurf");
22
22
23
23
const jsruntime = @import ("jsruntime" );
24
24
const Callback = jsruntime .Callback ;
25
+ const CallbackResult = jsruntime .CallbackResult ;
25
26
const Case = jsruntime .test_utils .Case ;
26
27
const checkCases = jsruntime .test_utils .checkCases ;
27
28
@@ -31,14 +32,35 @@ const NodeList = @import("nodelist.zig").NodeList;
31
32
32
33
pub const Interfaces = generate .Tuple (.{
33
34
MutationObserver ,
35
+ MutationRecord ,
36
+ MutationRecords ,
34
37
});
35
38
39
+ const Walker = @import ("../dom/walker.zig" ).WalkerChildren ;
40
+
41
+ const log = std .log .scoped (.events );
42
+
36
43
// WEB IDL https://dom.spec.whatwg.org/#interface-mutationobserver
37
44
pub const MutationObserver = struct {
38
45
cbk : Callback ,
46
+ observers : Observers ,
39
47
40
48
pub const mem_guarantied = true ;
41
49
50
+ const Observer = struct {
51
+ node : * parser.Node ,
52
+ options : MutationObserverInit ,
53
+ };
54
+
55
+ const deinitFunc = struct {
56
+ fn deinit (ctx : ? * anyopaque , alloc : std.mem.Allocator ) void {
57
+ const o : * Observer = @ptrCast (@alignCast (ctx ));
58
+ alloc .destroy (o );
59
+ }
60
+ }.deinit ;
61
+
62
+ const Observers = std .ArrayListUnmanaged (* Observer );
63
+
42
64
pub const MutationObserverInit = struct {
43
65
childList : bool = false ,
44
66
attributes : bool = false ,
@@ -48,24 +70,260 @@ pub const MutationObserver = struct {
48
70
characterDataOldValue : bool = false ,
49
71
// TODO
50
72
// attributeFilter: [][]const u8,
73
+
74
+ fn attr (self : MutationObserverInit ) bool {
75
+ return self .attributes or self .attributeOldValue ;
76
+ }
77
+
78
+ fn cdata (self : MutationObserverInit ) bool {
79
+ return self .characterData or self .characterDataOldValue ;
80
+ }
51
81
};
52
82
53
83
pub fn constructor (cbk : Callback ) ! MutationObserver {
54
84
return MutationObserver {
55
85
.cbk = cbk ,
86
+ .observers = .{},
56
87
};
57
88
}
58
89
59
- pub fn _observe (
60
- _ : * MutationObserver ,
61
- _ : * parser.Node ,
62
- _ : ? MutationObserverInit ,
63
- ) ! void {}
90
+ // TODO
91
+ fn resolveOptions (opt : ? MutationObserverInit ) MutationObserverInit {
92
+ return opt orelse .{};
93
+ }
94
+
95
+ pub fn _observe (self : * MutationObserver , alloc : std.mem.Allocator , node : * parser.Node , options : ? MutationObserverInit ) ! void {
96
+ const o = try alloc .create (Observer );
97
+ o .* = .{
98
+ .node = node ,
99
+ .options = resolveOptions (options ),
100
+ };
101
+ errdefer alloc .destroy (o );
102
+
103
+ // register the new observer.
104
+ try self .observers .append (alloc , o );
105
+
106
+ // register node's events.
107
+ if (o .options .childList or o .options .subtree ) {
108
+ try parser .eventTargetAddEventListener (
109
+ parser .toEventTarget (parser .Node , node ),
110
+ alloc ,
111
+ "DOMNodeInserted" ,
112
+ EventHandler ,
113
+ .{ .cbk = self .cbk , .data = o , .deinitFunc = deinitFunc },
114
+ false ,
115
+ );
116
+ try parser .eventTargetAddEventListener (
117
+ parser .toEventTarget (parser .Node , node ),
118
+ alloc ,
119
+ "DOMNodeRemoved" ,
120
+ EventHandler ,
121
+ .{ .cbk = self .cbk , .data = o , .deinitFunc = deinitFunc },
122
+ false ,
123
+ );
124
+ }
125
+ if (o .options .attr ()) {
126
+ try parser .eventTargetAddEventListener (
127
+ parser .toEventTarget (parser .Node , node ),
128
+ alloc ,
129
+ "DOMAttrModified" ,
130
+ EventHandler ,
131
+ .{ .cbk = self .cbk , .data = o , .deinitFunc = deinitFunc },
132
+ false ,
133
+ );
134
+ }
135
+ if (o .options .cdata ()) {
136
+ try parser .eventTargetAddEventListener (
137
+ parser .toEventTarget (parser .Node , node ),
138
+ alloc ,
139
+ "DOMCharacterDataModified" ,
140
+ EventHandler ,
141
+ .{ .cbk = self .cbk , .data = o , .deinitFunc = deinitFunc },
142
+ false ,
143
+ );
144
+ }
145
+ if (o .options .subtree ) {
146
+ try parser .eventTargetAddEventListener (
147
+ parser .toEventTarget (parser .Node , node ),
148
+ alloc ,
149
+ "DOMSubtreeModified" ,
150
+ EventHandler ,
151
+ .{ .cbk = self .cbk , .data = o , .deinitFunc = deinitFunc },
152
+ false ,
153
+ );
154
+ }
155
+ }
156
+
157
+ // TODO
158
+ pub fn _disconnect (_ : * MutationObserver ) ! void {
159
+ // TODO unregister listeners.
160
+ }
161
+
162
+ pub fn deinit (self : * MutationObserver , alloc : std.mem.Allocator ) void {
163
+ // TODO unregister listeners.
164
+ for (self .observers .items ) | o | alloc .destroy (o );
165
+ self .observers .deinit (alloc );
166
+ }
64
167
65
168
// TODO
66
- pub fn _disconnect (_ : * MutationObserver ) ! void {}
169
+ pub fn _takeRecords (_ : MutationObserver ) ? []const u8 {
170
+ return &[_ ]u8 {};
171
+ }
67
172
};
68
173
174
+ // Handle multiple record?
175
+ pub const MutationRecords = struct {
176
+ first : ? MutationRecord = null ,
177
+
178
+ pub const mem_guarantied = true ;
179
+
180
+ pub fn get_length (self : * MutationRecords ) u32 {
181
+ if (self .first == null ) return 0 ;
182
+
183
+ return 1 ;
184
+ }
185
+
186
+ pub fn postAttach (self : * MutationRecords , js_obj : jsruntime.JSObject ) ! void {
187
+ if (self .first ) | mr | {
188
+ try js_obj .set ("0" , mr );
189
+ }
190
+ }
191
+ };
192
+
193
+ pub const MutationRecord = struct {
194
+ type : []const u8 ,
195
+ target : * parser.Node ,
196
+ addedNodes : NodeList = NodeList .init (),
197
+ removedNodes : NodeList = NodeList .init (),
198
+ previousSibling : ? * parser.Node = null ,
199
+ nextSibling : ? * parser.Node = null ,
200
+ attributeName : ? []const u8 = null ,
201
+ attributeNamespace : ? []const u8 = null ,
202
+ oldValue : ? []const u8 = null ,
203
+
204
+ pub const mem_guarantied = true ;
205
+
206
+ pub fn get_type (self : MutationRecord ) []const u8 {
207
+ return self .type ;
208
+ }
209
+
210
+ pub fn get_addedNodes (self : MutationRecord ) NodeList {
211
+ return self .addedNodes ;
212
+ }
213
+
214
+ pub fn get_removedNodes (self : MutationRecord ) NodeList {
215
+ return self .addedNodes ;
216
+ }
217
+
218
+ pub fn get_target (self : MutationRecord ) * parser.Node {
219
+ return self .target ;
220
+ }
221
+
222
+ pub fn get_attributeName (self : MutationRecord ) ? []const u8 {
223
+ return self .attributeName ;
224
+ }
225
+
226
+ pub fn get_attributeNamespace (self : MutationRecord ) ? []const u8 {
227
+ return self .attributeNamespace ;
228
+ }
229
+
230
+ pub fn get_previousSibling (self : MutationRecord ) ? * parser.Node {
231
+ return self .previousSibling ;
232
+ }
233
+
234
+ pub fn get_nextSibling (self : MutationRecord ) ? * parser.Node {
235
+ return self .nextSibling ;
236
+ }
237
+
238
+ pub fn get_oldValue (self : MutationRecord ) ? []const u8 {
239
+ return self .oldValue ;
240
+ }
241
+ };
242
+
243
+ // EventHandler dedicated to mutation events.
244
+ const EventHandler = struct {
245
+ fn apply (o : * MutationObserver.Observer , target : * parser.Node ) bool {
246
+ // mutation on any target is always ok.
247
+ if (o .options .subtree ) return true ;
248
+ // if target equals node, alway ok.
249
+ if (target == o .node ) return true ;
250
+
251
+ // no subtree, no same target and no childlist, always noky.
252
+ if (! o .options .childList ) return false ;
253
+
254
+ // target must be a child of o.node
255
+ const walker = Walker {};
256
+ var next : ? * parser.Node = null ;
257
+ while (true ) {
258
+ next = walker .get_next (o .node , next ) catch break orelse break ;
259
+ if (next .? == target ) return true ;
260
+ }
261
+
262
+ return false ;
263
+ }
264
+
265
+ fn handle (evt : ? * parser.Event , data : parser.EventHandlerData ) void {
266
+ if (evt == null ) return ;
267
+
268
+ var mrs : MutationRecords = .{};
269
+
270
+ const t = parser .eventType (evt .? ) catch | e | {
271
+ log .err ("mutation observer event type: {any}" , .{e });
272
+ return ;
273
+ };
274
+ const et = parser .eventTarget (evt .? ) catch | e | {
275
+ log .err ("mutation observer event target: {any}" , .{e });
276
+ return ;
277
+ } orelse return ;
278
+ const node = parser .eventTargetToNode (et );
279
+
280
+ // retrieve the observer from the data.
281
+ const o : * MutationObserver.Observer = @ptrCast (@alignCast (data .data ));
282
+
283
+ if (! apply (o , node )) return ;
284
+
285
+ const muevt = parser .eventToMutationEvent (evt .? );
286
+
287
+ if (std .mem .eql (u8 , t , "DOMAttrModified" )) {
288
+ mrs .first = .{
289
+ .type = "attributes" ,
290
+ .target = o .node ,
291
+ .attributeName = parser .mutationEventAttributeName (muevt ) catch null ,
292
+ };
293
+
294
+ // record old value if required.
295
+ if (o .options .attributeOldValue ) {
296
+ mrs .first .? .oldValue = parser .mutationEventPrevValue (muevt ) catch null ;
297
+ }
298
+ } else if (std .mem .eql (u8 , t , "DOMCharacterDataModified" )) {
299
+ mrs .first = .{
300
+ .type = "characterData" ,
301
+ .target = o .node ,
302
+ };
303
+
304
+ // record old value if required.
305
+ if (o .options .characterDataOldValue ) {
306
+ mrs .first .? .oldValue = parser .mutationEventPrevValue (muevt ) catch null ;
307
+ }
308
+ } else {
309
+ return ;
310
+ }
311
+
312
+ // TODO get the allocator by another way?
313
+ var res = CallbackResult .init (data .cbk .nat_ctx .alloc );
314
+ defer res .deinit ();
315
+
316
+ // TODO pass MutationRecords and MutationObserver
317
+ data .cbk .trycall (.{mrs }, & res ) catch | e | log .err ("mutation event handler error: {any}" , .{e });
318
+
319
+ // in case of function error, we log the result and the trace.
320
+ if (! res .success ) {
321
+ log .info ("mutation observer event handler error: {s}" , .{res .result orelse "unknown" });
322
+ log .debug ("{s}" , .{res .stack orelse "no stack trace" });
323
+ }
324
+ }
325
+ }.handle ;
326
+
69
327
pub fn testExecFn (
70
328
_ : std.mem.Allocator ,
71
329
js_env : * jsruntime.Env ,
@@ -74,4 +332,44 @@ pub fn testExecFn(
74
332
.{ .src = "new MutationObserver(() => {}).observe(document, { childList: true });" , .ex = "undefined" },
75
333
};
76
334
try checkCases (js_env , & constructor );
335
+
336
+ var attr = [_ ]Case {
337
+ .{ .src =
338
+ \\var nb = 0;
339
+ \\var mrs;
340
+ \\new MutationObserver((mu) => {
341
+ \\ mrs = mu;
342
+ \\ nb++;
343
+ \\}).observe(document.firstElementChild, { attributes: true, attributeOldValue: true });
344
+ \\document.firstElementChild.setAttribute("foo", "bar");
345
+ \\// ignored b/c it's about another target.
346
+ \\document.firstElementChild.firstChild.setAttribute("foo", "bar");
347
+ \\nb;
348
+ , .ex = "1" },
349
+ .{ .src = "mrs[0].type" , .ex = "attributes" },
350
+ .{ .src = "mrs[0].target == document.firstElementChild" , .ex = "true" },
351
+ .{ .src = "mrs[0].target.getAttribute('foo')" , .ex = "bar" },
352
+ .{ .src = "mrs[0].attributeName" , .ex = "foo" },
353
+ .{ .src = "mrs[0].oldValue" , .ex = "null" },
354
+ };
355
+ try checkCases (js_env , & attr );
356
+
357
+ var cdata = [_ ]Case {
358
+ .{ .src =
359
+ \\var node = document.getElementById("para").firstChild;
360
+ \\var nb2 = 0;
361
+ \\var mrs2;
362
+ \\new MutationObserver((mu) => {
363
+ \\ mrs2 = mu;
364
+ \\ nb2++;
365
+ \\}).observe(node, { characterData: true, characterDataOldValue: true });
366
+ \\node.data = "foo";
367
+ \\nb2;
368
+ , .ex = "1" },
369
+ .{ .src = "mrs2[0].type" , .ex = "characterData" },
370
+ .{ .src = "mrs2[0].target == node" , .ex = "true" },
371
+ .{ .src = "mrs2[0].target.data" , .ex = "foo" },
372
+ .{ .src = "mrs2[0].oldValue" , .ex = " And" },
373
+ };
374
+ try checkCases (js_env , & cdata );
77
375
}
0 commit comments