1717// along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
1919const std = @import ("std" );
20+ const log = @import ("../../log.zig" );
2021const parser = @import ("../netsurf.zig" );
22+ const Env = @import ("../env.zig" ).Env ;
23+ const Page = @import ("../page.zig" ).Page ;
24+ const Loop = @import ("../../runtime/loop.zig" ).Loop ;
2125const EventTarget = @import ("../dom/event_target.zig" ).EventTarget ;
2226
2327pub const Interfaces = .{
2428 AbortController ,
25- Signal ,
29+ AbortSignal ,
2630};
2731
2832const AbortController = @This ();
2933
30- signal : ? Signal = null ,
34+ signal : * AbortSignal ,
3135
32- pub fn constructor () AbortController {
33- return .{};
34- }
36+ pub fn constructor (page : * Page ) ! AbortController {
37+ // Why do we allocate this rather than storing directly in the struct?
38+ // https://github.com/lightpanda-io/project/discussions/165
39+ const signal = try page .arena .create (AbortSignal );
40+ signal .* = .init ;
3541
36- pub fn get_signal (self : * AbortController ) * Signal {
37- if (self .signal ) | * s | {
38- return s ;
39- }
40- self .signal = .init ;
41- return & self .signal .? ;
42+ return .{
43+ .signal = signal ,
44+ };
4245}
4346
44- pub fn abort (self : * AbortController , reason_ : ? []const u8 ) void {
45- const signal = & self .signal ;
46-
47- signal .aborted = true ;
48- signal .reason = reason_ orelse "AbortError" ;
47+ pub fn get_signal (self : * AbortController ) * AbortSignal {
48+ return self .signal ;
49+ }
4950
50- const abort_event = try parser .eventCreate ();
51- defer parser .eventDestroy (abort_event );
52- try parser .eventInit (abort_event , "abort" , .{});
53- _ = try parser .eventTargetDispatchEvent (
54- parser .toEventTarget (Signal , signal ),
55- abort_event ,
56- );
51+ pub fn _abort (self : * AbortController , reason_ : ? []const u8 ) ! void {
52+ return self .signal .abort (reason_ );
5753}
5854
59- pub const Signal = struct {
55+ pub const AbortSignal = struct {
56+ const DEFAULT_REASON = "AbortError" ;
57+
6058 pub const prototype = * EventTarget ;
59+ proto : parser.EventTargetTBase = .{},
6160
6261 aborted : bool ,
6362 reason : ? []const u8 ,
64- proto : parser.EventTargetTBase ,
6563
66- pub const init : Signal = .{
64+ pub const init : AbortSignal = .{
6765 .proto = .{},
6866 .reason = null ,
6967 .aborted = false ,
7068 };
7169
72- pub fn get_aborted (self : * const Signal ) bool {
70+ pub fn static_abort (reason_ : ? []const u8 ) AbortSignal {
71+ return .{
72+ .aborted = true ,
73+ .reason = reason_ orelse DEFAULT_REASON ,
74+ };
75+ }
76+
77+ pub fn static_timeout (delay : u32 , page : * Page ) ! * AbortSignal {
78+ const callback = try page .arena .create (TimeoutCallback );
79+ callback .* = .{
80+ .signal = .init ,
81+ .node = .{ .func = TimeoutCallback .run },
82+ };
83+
84+ const delay_ms : u63 = @as (u63 , delay ) * std .time .ns_per_ms ;
85+ _ = try page .loop .timeout (delay_ms , & callback .node );
86+ return & callback .signal ;
87+ }
88+
89+ pub fn get_aborted (self : * const AbortSignal ) bool {
7390 return self .aborted ;
7491 }
7592
93+ fn abort (self : * AbortSignal , reason_ : ? []const u8 ) ! void {
94+ self .aborted = true ;
95+ self .reason = reason_ orelse DEFAULT_REASON ;
96+
97+ const abort_event = try parser .eventCreate ();
98+ try parser .eventSetInternalType (abort_event , .abort_signal );
99+
100+ defer parser .eventDestroy (abort_event );
101+ try parser .eventInit (abort_event , "abort" , .{});
102+ _ = try parser .eventTargetDispatchEvent (
103+ parser .toEventTarget (AbortSignal , self ),
104+ abort_event ,
105+ );
106+ }
107+
76108 const Reason = union (enum ) {
77109 reason : []const u8 ,
78110 undefined : void ,
79111 };
80- pub fn get_reason (self : * const Signal ) Reason {
112+ pub fn get_reason (self : * const AbortSignal ) Reason {
81113 if (self .reason ) | r | {
82114 return .{ .reason = r };
83115 }
84116 return .{ .undefined = {} };
85117 }
118+
119+ const ThrowIfAborted = union (enum ) {
120+ exception : Env.Exception ,
121+ undefined : void ,
122+ };
123+ pub fn _throwIfAborted (self : * const AbortSignal , page : * Page ) ThrowIfAborted {
124+ if (self .aborted ) {
125+ const ex = page .main_context .throw (self .reason orelse DEFAULT_REASON );
126+ return .{ .exception = ex };
127+ }
128+ return .{ .undefined = {} };
129+ }
130+ };
131+
132+ const TimeoutCallback = struct {
133+ signal : AbortSignal ,
134+
135+ // This is the internal data that the event loop tracks. We'll get this
136+ // back in run and, from it, can get our TimeoutCallback instance
137+ node : Loop.CallbackNode = undefined ,
138+
139+ fn run (node : * Loop.CallbackNode , _ : * ? u63 ) void {
140+ const self : * TimeoutCallback = @fieldParentPtr ("node" , node );
141+ self .signal .abort ("TimeoutError" ) catch | err | {
142+ log .warn (.app , "abort signal timeout" , .{ .err = err });
143+ };
144+ }
86145};
87146
88147const testing = @import ("../../testing.zig" );
@@ -91,22 +150,39 @@ test "Browser.HTML.AbortController" {
91150 defer runner .deinit ();
92151
93152 try runner .testCases (&.{
94- .{ "var called = false " , null },
153+ .{ "var called = 0 " , null },
95154 .{ "var a1 = new AbortController()" , null },
96155 .{ "var s1 = a1.signal" , null },
156+ .{ "s1.throwIfAborted()" , "undefined" },
97157 .{ "s1.reason" , "undefined" },
98158 .{ "var target;" , null },
99159 .{
100160 \\ s1.addEventListener('abort', (e) => {
101- \\ called = 1;
161+ \\ called + = 1;
102162 \\ target = e.target;
103163 \\
104164 \\ });
105- \\ target == s1
106- , "true" },
165+ ,
166+ null ,
167+ },
107168 .{ "a1.abort()" , null },
108169 .{ "s1.aborted" , "true" },
109- .{ "s1.reason" , "undefined" },
170+ .{ "target == s1" , "true" },
171+ .{ "s1.reason" , "AbortError" },
110172 .{ "called" , "1" },
111173 }, .{});
174+
175+ try runner .testCases (&.{
176+ .{ "var s2 = AbortSignal.abort('over 9000')" , null },
177+ .{ "s2.aborted" , "true" },
178+ .{ "s2.reason" , "over 9000" },
179+ .{ "AbortSignal.abort().reason" , "AbortError" },
180+ }, .{});
181+
182+ try runner .testCases (&.{
183+ .{ "var s3 = AbortSignal.timeout(10)" , null },
184+ .{ "s3.aborted" , "true" },
185+ .{ "s3.reason" , "TimeoutError" },
186+ .{ "try { s3.throwIfAborted() } catch (e) { e }" , "Error: TimeoutError" },
187+ }, .{});
112188}
0 commit comments