diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index da7362aa..cd775332 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -545,13 +545,13 @@ pub fn dynamicModuleCallback( break :blk js.String.toSliceZ(.{ .local = &local, .handle = resource_name.? }) catch |err| { log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback1" }); - return @constCast((local.rejectPromise("Out of memory") catch return null).handle); + return @constCast(local.rejectPromise(.{ .generic_error = "Out of memory" }).handle); }; }; const specifier = js.String.toSliceZ(.{ .local = &local, .handle = v8_specifier.? }) catch |err| { log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback2" }); - return @constCast((local.rejectPromise("Out of memory") catch return null).handle); + return @constCast(local.rejectPromise(.{ .generic_error = "Out of memory" }).handle); }; const normalized_specifier = self.script_manager.?.resolveSpecifier( @@ -560,14 +560,14 @@ pub fn dynamicModuleCallback( specifier, ) catch |err| { log.err(.app, "OOM", .{ .err = err, .src = "dynamicModuleCallback3" }); - return @constCast((local.rejectPromise("Out of memory") catch return null).handle); + return @constCast(local.rejectPromise(.{ .generic_error = "Out of memory" }).handle); }; const promise = self._dynamicModuleCallback(normalized_specifier, resource, &local) catch |err| blk: { log.err(.js, "dynamic module callback", .{ .err = err, }); - break :blk local.rejectPromise("Failed to load module") catch return null; + break :blk local.rejectPromise(.{ .generic_error = "Out of memory" }); }; return @constCast(promise.handle); } diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index f913dafd..af7d1116 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -1206,9 +1206,9 @@ pub fn stackTrace(self: *const Local) !?[]const u8 { } // == Promise Helpers == -pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise { +pub fn rejectPromise(self: *const Local, comptime kind: js.PromiseResolver.RejectError) js.Promise { var resolver = js.PromiseResolver.init(self); - resolver.reject("Local.rejectPromise", value); + resolver.rejectError("Local.rejectPromise", kind); return resolver.promise(); } diff --git a/src/browser/js/PromiseResolver.zig b/src/browser/js/PromiseResolver.zig index 6386569a..7743fa48 100644 --- a/src/browser/js/PromiseResolver.zig +++ b/src/browser/js/PromiseResolver.zig @@ -22,6 +22,8 @@ const v8 = js.v8; const log = @import("../../log.zig"); const DOMException = @import("../webapi/DOMException.zig"); +const DOMException = @import("../webapi/DOMException.zig"); + const PromiseResolver = @This(); local: *const js.Local, @@ -65,20 +67,43 @@ pub fn reject(self: PromiseResolver, comptime source: []const u8, value: anytype }; } -pub const RejectError = union(enum) { - generic: []const u8, +pub const RejectError = union(enum(u4)) { + /// Not to be confused with `DOMException`; this is bare `Error`. + generic_error: []const u8, + range_error: []const u8, + reference_error: []const u8, + syntax_error: []const u8, type_error: []const u8, - dom_exception: anyerror, + wasm_compile_error: void, // TODO. + wasm_link_error: void, // TODO. + wasm_runtime_error: void, // TODO. + wasm_suspend_error: void, // TODO. + /// DOM exceptions are unknown to V8, belongs to web standards. + dom_exception: struct { err: anyerror }, }; -pub fn rejectError(self: PromiseResolver, comptime source: []const u8, err: RejectError) void { - const handle = switch (err) { - .type_error => |str| self.local.isolate.createTypeError(str), - .generic => |str| self.local.isolate.createError(str), + +/// Rejects the promise w/ an error object. +pub fn rejectError( + self: PromiseResolver, + comptime source: []const u8, + comptime kind: RejectError, +) void { + const handle = switch (kind) { + .generic_error => |msg| self.local.isolate.createError(msg), + .range_error => |msg| self.local.isolate.createRangeError(msg), + .reference_error => |msg| self.local.isolate.createReferenceError(msg), + .syntax_error => |msg| self.local.isolate.createSyntaxError(msg), + .type_error => |msg| self.local.isolate.createTypeError(msg), + // "Exceptional". .dom_exception => |exception| { - self.reject(source, DOMException.fromError(exception)); + self._reject(DOMException.fromError(exception.err) orelse unreachable) catch |reject_err| { + log.err(.bug, "rejectDomException", .{ .source = source, .err = reject_err, .persistent = false }); + }; return; }, + inline else => unreachable, }; + self._reject(js.Value{ .handle = handle, .local = self.local }) catch |reject_err| { log.err(.bug, "rejectError", .{ .source = source, .err = reject_err, .persistent = false }); }; diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 6702a52e..25ed8857 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -125,8 +125,8 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, page: *Page) return local.resolvePromise(definition.constructor); } - validateName(name) catch |err| { - return local.rejectPromise(DOMException.fromError(err) orelse unreachable); + validateName(name) catch |err| switch (err) { + error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }), }; const gop = try self._when_defined.getOrPut(page.arena, name); diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index 4839a69a..7d6df2e3 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -96,8 +96,8 @@ pub fn generateKey( key_usages: []const []const u8, page: *Page, ) !js.Promise { - const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch |err| { - return page.js.local.?.rejectPromise(@errorName(err)); + const key_or_pair = CryptoKey.init(algorithm, extractable, key_usages, page) catch { + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }); }; return page.js.local.?.resolvePromise(key_or_pair); @@ -112,7 +112,7 @@ pub fn exportKey( page: *Page, ) !js.Promise { if (!key.canExportKey()) { - return error.InvalidAccessError; + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); } if (std.mem.eql(u8, format, "raw")) { @@ -124,9 +124,10 @@ pub fn exportKey( if (is_unsupported) { log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format }); + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); } - return page.js.local.?.rejectPromise(@errorName(error.NotSupported)); + return page.js.local.?.rejectPromise(.{ .type_error = "invalid format" }); } /// Derive a secret key from a master key. @@ -148,7 +149,7 @@ pub fn deriveBits( log.warn(.not_implemented, "SubtleCrypto.deriveBits", .{ .name = name }); } - return page.js.local.?.rejectPromise(@errorName(error.NotSupported)); + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); }, }; } @@ -185,19 +186,19 @@ pub fn sign( .hmac => { // Verify algorithm. if (!algorithm.isHMAC()) { - return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError)); + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); } // Call sign for HMAC. - const result = key.signHMAC(data, page) catch |err| { - return page.js.local.?.rejectPromise(@errorName(err)); + const result = key.signHMAC(data, page) catch { + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); }; return page.js.local.?.resolvePromise(result); }, else => { log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type }); - return page.js.local.?.rejectPromise(@errorName(error.InvalidAccessError)); + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); }, }; } @@ -211,18 +212,20 @@ pub fn verify( data: []const u8, // ArrayBuffer. page: *Page, ) !js.Promise { - if (!algorithm.isHMAC()) return error.InvalidAccessError; + if (!algorithm.isHMAC()) { + return page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); + } return switch (key._type) { .hmac => key.verifyHMAC(signature, data, page), - else => return error.InvalidAccessError, + else => page.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }), }; } pub fn digest(_: *const SubtleCrypto, algorithm: []const u8, data: js.TypedArray(u8), page: *Page) !js.Promise { const local = page.js.local.?; if (algorithm.len > 10) { - return local.rejectPromise(DOMException.fromError(error.NotSupported)); + return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); } const normalized = std.ascii.lowerString(&page.buf, algorithm); if (std.mem.eql(u8, normalized, "sha-1")) { @@ -245,7 +248,7 @@ pub fn digest(_: *const SubtleCrypto, algorithm: []const u8, data: js.TypedArray Sha512.hash(data.values, page.buf[0..Sha512.digest_length], .{}); return local.resolvePromise(js.ArrayBuffer{ .values = page.buf[0..Sha512.digest_length] }); } - return local.rejectPromise(DOMException.fromError(error.NotSupported)); + return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); } /// Returns the desired digest by its name. diff --git a/src/browser/webapi/net/Fetch.zig b/src/browser/webapi/net/Fetch.zig index 0a44aae2..de1af4ec 100644 --- a/src/browser/webapi/net/Fetch.zig +++ b/src/browser/webapi/net/Fetch.zig @@ -243,7 +243,7 @@ fn httpErrorCallback(ctx: *anyopaque, err: anyerror) void { defer ls.deinit(); // fetch() must reject with a TypeError on network errors per spec - ls.toLocal(self._resolver).rejectError("fetch error", .{ .type_error = @errorName(err) }); + ls.toLocal(self._resolver).rejectError("fetch error", .{ .type_error = "fetch error" }); } fn httpShutdownCallback(ctx: *anyopaque) void { diff --git a/src/browser/webapi/net/Request.zig b/src/browser/webapi/net/Request.zig index 6ba8f224..81ef8ba9 100644 --- a/src/browser/webapi/net/Request.zig +++ b/src/browser/webapi/net/Request.zig @@ -192,8 +192,8 @@ pub fn text(self: *const Request, page: *Page) !js.Promise { pub fn json(self: *const Request, page: *Page) !js.Promise { const body = self._body orelse ""; const local = page.js.local.?; - const value = local.parseJSON(body) catch |err| { - return local.rejectPromise(.{@errorName(err)}); + const value = local.parseJSON(body) catch { + return local.rejectPromise(.{ .syntax_error = "failed to parse" }); }; return local.resolvePromise(try value.persist()); } diff --git a/src/browser/webapi/net/Response.zig b/src/browser/webapi/net/Response.zig index 6a926369..7020ac05 100644 --- a/src/browser/webapi/net/Response.zig +++ b/src/browser/webapi/net/Response.zig @@ -139,8 +139,8 @@ pub fn getText(self: *const Response, page: *Page) !js.Promise { pub fn getJson(self: *Response, page: *Page) !js.Promise { const body = self._body orelse ""; const local = page.js.local.?; - const value = local.parseJSON(body) catch |err| { - return local.rejectPromise(.{@errorName(err)}); + const value = local.parseJSON(body) catch { + return local.rejectPromise(.{ .syntax_error = "failed to parse" }); }; return local.resolvePromise(try value.persist()); } diff --git a/src/browser/webapi/streams/ReadableStream.zig b/src/browser/webapi/streams/ReadableStream.zig index e4e5d0f9..56d5e9eb 100644 --- a/src/browser/webapi/streams/ReadableStream.zig +++ b/src/browser/webapi/streams/ReadableStream.zig @@ -255,7 +255,7 @@ pub fn pipeThrough(self: *ReadableStream, transform: PipeTransform, page: *Page) /// Returns a promise that resolves when piping is complete. pub fn pipeTo(self: *ReadableStream, destination: *WritableStream, page: *Page) !js.Promise { if (self.getLocked()) { - return page.js.local.?.rejectPromise("ReadableStream is locked"); + return page.js.local.?.rejectPromise(.{ .type_error = "ReadableStream is locked" }); } const local = page.js.local.?; diff --git a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig index 2d3c5bbe..7bd848b7 100644 --- a/src/browser/webapi/streams/ReadableStreamDefaultReader.zig +++ b/src/browser/webapi/streams/ReadableStreamDefaultReader.zig @@ -58,12 +58,12 @@ pub const ReadResult = struct { pub fn read(self: *ReadableStreamDefaultReader, page: *Page) !js.Promise { const stream = self._stream orelse { - return page.js.local.?.rejectPromise("Reader has been released"); + return page.js.local.?.rejectPromise(.{ .type_error = "Reader has been released" }); }; if (stream._state == .errored) { - const err = stream._stored_error orelse "Stream errored"; - return page.js.local.?.rejectPromise(err); + //const err = stream._stored_error orelse "Stream errored"; + return page.js.local.?.rejectPromise(.{ .type_error = "Stream errored" }); } if (stream._controller.dequeue()) |chunk| { @@ -95,7 +95,7 @@ pub fn releaseLock(self: *ReadableStreamDefaultReader) void { pub fn cancel(self: *ReadableStreamDefaultReader, reason_: ?[]const u8, page: *Page) !js.Promise { const stream = self._stream orelse { - return page.js.local.?.rejectPromise("Reader has been released"); + return page.js.local.?.rejectPromise(.{ .type_error = "Reader has been released" }); }; self.releaseLock(); diff --git a/src/browser/webapi/streams/WritableStreamDefaultWriter.zig b/src/browser/webapi/streams/WritableStreamDefaultWriter.zig index 8ed552ea..f5e5e610 100644 --- a/src/browser/webapi/streams/WritableStreamDefaultWriter.zig +++ b/src/browser/webapi/streams/WritableStreamDefaultWriter.zig @@ -32,11 +32,11 @@ pub fn init(stream: *WritableStream, page: *Page) !*WritableStreamDefaultWriter pub fn write(self: *WritableStreamDefaultWriter, chunk: js.Value, page: *Page) !js.Promise { const stream = self._stream orelse { - return page.js.local.?.rejectPromise("Writer has been released"); + return page.js.local.?.rejectPromise(.{ .type_error = "Writer has been released" }); }; if (stream._state != .writable) { - return page.js.local.?.rejectPromise("Stream is not writable"); + return page.js.local.?.rejectPromise(.{ .type_error = "Stream is not writable" }); } try stream.writeChunk(chunk, page); @@ -46,11 +46,11 @@ pub fn write(self: *WritableStreamDefaultWriter, chunk: js.Value, page: *Page) ! pub fn close(self: *WritableStreamDefaultWriter, page: *Page) !js.Promise { const stream = self._stream orelse { - return page.js.local.?.rejectPromise("Writer has been released"); + return page.js.local.?.rejectPromise(.{ .type_error = "Writer has been released" }); }; if (stream._state != .writable) { - return page.js.local.?.rejectPromise("Stream is not writable"); + return page.js.local.?.rejectPromise(.{ .type_error = "Stream is not writable" }); } try stream.closeStream(page); @@ -67,7 +67,7 @@ pub fn releaseLock(self: *WritableStreamDefaultWriter) void { pub fn getClosed(self: *WritableStreamDefaultWriter, page: *Page) !js.Promise { const stream = self._stream orelse { - return page.js.local.?.rejectPromise("Writer has been released"); + return page.js.local.?.rejectPromise(.{ .type_error = "Writer has been released" }); }; if (stream._state == .closed) {