@@ -21,6 +21,7 @@ const std = @import("std");
2121const parser = @import ("../netsurf.zig" );
2222const Callback = @import ("../env.zig" ).Callback ;
2323const SessionState = @import ("../env.zig" ).SessionState ;
24+ const Loop = @import ("../../runtime/loop.zig" ).Loop ;
2425
2526const Navigator = @import ("navigator.zig" ).Navigator ;
2627const History = @import ("history.zig" ).History ;
@@ -31,6 +32,8 @@ const EventTarget = @import("../dom/event_target.zig").EventTarget;
3132
3233const storage = @import ("../storage/storage.zig" );
3334
35+ const log = std .log .scoped (.window );
36+
3437// https://dom.spec.whatwg.org/#interface-window-extensions
3538// https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
3639pub const Window = struct {
@@ -45,10 +48,9 @@ pub const Window = struct {
4548 location : Location = .{},
4649 storage_shelf : ? * storage.Shelf = null ,
4750
48- // store a map between internal timeouts ids and pointers to uint.
49- // the maximum number of possible timeouts is fixed.
50- timeoutid : u32 = 0 ,
51- timeoutids : [512 ]u64 = undefined ,
51+ // counter for having unique timer ids
52+ timer_id : u31 = 0 ,
53+ timers : std .AutoHashMapUnmanaged (u32 , * TimerCallback ) = .{},
5254
5355 crypto : Crypto = .{},
5456 console : Console = .{},
@@ -129,23 +131,93 @@ pub const Window = struct {
129131
130132 // TODO handle callback arguments.
131133 pub fn _setTimeout (self : * Window , cbk : Callback , delay : ? u32 , state : * SessionState ) ! u32 {
132- if (self .timeoutid >= self .timeoutids .len ) return error .TooMuchTimeout ;
134+ return self .createTimeout (cbk , delay , state , false );
135+ }
133136
134- const ddelay : u63 = delay orelse 0 ;
135- const id = try state .loop .timeout (ddelay * std .time .ns_per_ms , cbk );
137+ // TODO handle callback arguments.
138+ pub fn _setInterval (self : * Window , cbk : Callback , delay : ? u32 , state : * SessionState ) ! u32 {
139+ return self .createTimeout (cbk , delay , state , true );
140+ }
136141
137- self .timeoutids [self .timeoutid ] = id ;
138- defer self .timeoutid += 1 ;
142+ pub fn _clearTimeout (self : * Window , id : u32 , state : * SessionState ) ! void {
143+ const kv = self .timers .fetchRemove (id ) orelse return ;
144+ try state .loop .cancel (kv .value .loop_id );
145+ }
139146
140- return self .timeoutid ;
147+ pub fn _clearInterval (self : * Window , id : u32 , state : * SessionState ) ! void {
148+ const kv = self .timers .fetchRemove (id ) orelse return ;
149+ try state .loop .cancel (kv .value .loop_id );
141150 }
142151
143- pub fn _clearTimeout (self : * Window , id : u32 , state : * SessionState ) ! void {
144- // I do would prefer return an error in this case, but it seems some JS
145- // uses invalid id, in particular id 0.
146- // So we silently ignore invalid id for now.
147- if (id >= self .timeoutid ) return ;
152+ pub fn createTimeout (self : * Window , cbk : Callback , delay_ : ? u32 , state : * SessionState , comptime repeat : bool ) ! u32 {
153+ if (self .timers .count () > 512 ) {
154+ return error .TooManyTimeout ;
155+ }
156+ const timer_id = self .timer_id +% 1 ;
157+ self .timer_id = timer_id ;
158+
159+ const arena = state .arena ;
160+
161+ const gop = try self .timers .getOrPut (arena , timer_id );
162+ if (gop .found_existing ) {
163+ // this can only happen if we've created 2^31 timeouts.
164+ return error .TooManyTimeout ;
165+ }
166+ errdefer _ = self .timers .remove (timer_id );
167+
168+ const delay : u63 = (delay_ orelse 0 ) * std .time .ns_per_ms ;
169+ const callback = try arena .create (TimerCallback );
170+
171+ callback .* = .{
172+ .cbk = cbk ,
173+ .loop_id = 0 , // we're going to set this to a real value shortly
174+ .window = self ,
175+ .timer_id = timer_id ,
176+ .node = .{ .func = TimerCallback .run },
177+ .repeat = if (repeat ) delay else null ,
178+ };
179+ callback .loop_id = try state .loop .timeout (delay , & callback .node );
180+
181+ gop .value_ptr .* = callback ;
182+ return timer_id ;
183+ }
184+ };
185+
186+ const TimerCallback = struct {
187+ // the internal loop id, need it when cancelling
188+ loop_id : usize ,
189+
190+ // the id of our timer (windows.timers key)
191+ timer_id : u31 ,
192+
193+ // The JavaScript callback to execute
194+ cbk : Callback ,
195+
196+ // This is the internal data that the event loop tracks. We'll get this
197+ // back in run and, from it, can get our TimerCallback instance
198+ node : Loop.CallbackNode = undefined ,
199+
200+ // if the event should be repeated
201+ repeat : ? u63 = null ,
202+
203+ window : * Window ,
204+
205+ fn run (node : * Loop.CallbackNode , repeat_delay : * ? u63 ) void {
206+ const self : * TimerCallback = @fieldParentPtr ("node" , node );
207+
208+ var result : Callback.Result = undefined ;
209+ self .cbk .tryCall (.{}, & result ) catch {
210+ log .err ("timeout callback error: {s}" , .{result .exception });
211+ log .debug ("stack:\n {s}" , .{result .stack orelse "???" });
212+ };
213+
214+ if (self .repeat ) | r | {
215+ // setInterval
216+ repeat_delay .* = r ;
217+ return ;
218+ }
148219
149- try state .loop .cancel (self .timeoutids [id ], null );
220+ // setTimeout
221+ _ = self .window .timers .remove (self .timer_id );
150222 }
151223};
0 commit comments