From f7fbd217caffffac4e470294d8beb29279638313 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 4 Sep 2026 18:25:20 +0800 Subject: [PATCH] chore: leverage bridge for error rejection https://github.com/lightpanda-io/browser/pull/3095 made the bridge more promise- aware. A WebAPI with a `!js.Promise` return type that returns an error will not reject the promise. That PR was limited to Crypto. This expands it where possible. Generally speaking, the result is just more idiomatic Zig code. From: ```zig validateName(name) catch |err| switch (err) { error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }), } ``` to: ```zig try validateName(name); ``` However, because TypeErrors must often reject with a specific message, we use a pseudo-global in Env. So, you'd do: ```zig .invalid => return local.typeError("invalid algorithm"), ``` which return error.TypeError AND stores "invalid algorithm" on the `Env`. When `Caller` gets `error.TypeError` it checks the Env for a message. Who says Zig errors can't carry payloads?! ;# Please enter the commit message for your changes. Lines starting --- src/browser/js/Caller.zig | 5 +- src/browser/js/Env.zig | 4 + src/browser/js/Local.zig | 9 ++ src/browser/tests/net/response.html | 10 +- src/browser/webapi/CustomElementRegistry.zig | 4 +- src/browser/webapi/SubtleCrypto.zig | 115 +++++------------- src/browser/webapi/crypto/AES.zig | 12 +- src/browser/webapi/crypto/EC.zig | 16 ++- src/browser/webapi/crypto/HMAC.zig | 24 ++-- src/browser/webapi/crypto/X25519.zig | 8 +- src/browser/webapi/net/Request.zig | 43 +++---- src/browser/webapi/net/Response.zig | 47 +++---- src/browser/webapi/storage/idb/IDBFactory.zig | 2 +- 13 files changed, 113 insertions(+), 186 deletions(-) diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 8a1636f28a..5978897269 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -585,6 +585,9 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any } const err_local = errorLocal(T, local, info); + const env = local.ctx.env; + const message = env.error_message orelse ""; + env.error_message = null; const js_err: *const v8.Value = blk: { // Error constructors use the isolate's current context: enter the @@ -595,7 +598,7 @@ fn handleError(comptime T: type, comptime F: type, local: *const Local, err: any break :blk switch (err) { error.InvalidArgument => isolate.createTypeError("invalid argument"), - error.TypeError => isolate.createTypeError(""), + error.TypeError => isolate.createTypeError(message), error.RangeError => isolate.createRangeError(""), error.OutOfMemory => isolate.createError("out of memory"), error.IllegalConstructor => isolate.createError("Illegal Constructor"), diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index fae6096f22..8dec568549 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -109,6 +109,10 @@ tearing_down: bool = false, heap_limit_protected: bool = false, +// Message for the next TypeError the bridge builds. Set by local.typeError. +// Think of it as our own little global errno. How cute. +error_message: ?[]const u8 = null, + pub const InitOpts = struct { with_inspector: bool = false, }; diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index 39ee9f44aa..db78a211ca 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -1512,6 +1512,15 @@ pub fn stackTrace(self: *const Local) !?[]const u8 { return buf.written(); } +// We sometimes need to reject with a specific TypeError message. We can't +// attach an anything to `error.TypeError`, but we can use a pseudo-global. +// When caller catches the error.TypeError, it'll look into env.error_message +// for the message. +pub fn typeError(self: *const Local, message: []const u8) error{TypeError} { + self.ctx.env.error_message = message; + return error.TypeError; +} + // == Promise Helpers == pub fn rejectPromise(self: *const Local, err: js.PromiseResolver.RejectError) js.Promise { var resolver = js.PromiseResolver.init(self); diff --git a/src/browser/tests/net/response.html b/src/browser/tests/net/response.html index 6fd33d006b..fc01dccd15 100644 --- a/src/browser/tests/net/response.html +++ b/src/browser/tests/net/response.html @@ -291,9 +291,10 @@ await response1.text(); const usedAfter1 = response1.bodyUsed; - // Re-consuming a used body rejects. - let rejected1 = false; - try { await response1.text(); } catch (e) { rejected1 = true; } + // Re-consuming a used body rejects with a TypeError (a rejection, not a + // synchronous throw), carrying the message the bridge was handed. + let rejected1 = null; + try { await response1.text(); } catch (e) { rejected1 = e; } // A bodyless response is never "used". const response2 = new Response(); @@ -309,7 +310,8 @@ await state.done(() => { testing.expectFalse(usedBefore1); testing.expectTrue(usedAfter1); - testing.expectTrue(rejected1); + testing.expectEqual('TypeError', rejected1.constructor.name); + testing.expectEqual('Body has already been read', rejected1.message); testing.expectFalse(usedBefore2); testing.expectFalse(usedAfter2); diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index b963cf04a6..25f8ca25be 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -157,9 +157,7 @@ pub fn whenDefined(self: *CustomElementRegistry, name: []const u8, frame: *Frame return local.resolvePromise(definition.constructor); } - validateName(name) catch |err| switch (err) { - error.SyntaxError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.SyntaxError } }), - }; + try validateName(name); const gop = try self._when_defined.getOrPut(frame.arena, name); if (gop.found_existing) { diff --git a/src/browser/webapi/SubtleCrypto.zig b/src/browser/webapi/SubtleCrypto.zig index f6b030a9e9..dbf84dd89e 100644 --- a/src/browser/webapi/SubtleCrypto.zig +++ b/src/browser/webapi/SubtleCrypto.zig @@ -59,17 +59,15 @@ pub fn generateKey( .aes_key_gen => |params| return AES.generate(params, extractable, key_usages, exec), .ec_key_gen => |params| return EC.generate(params, extractable, key_usages, exec), .rsa_hashed_key_gen => |params| { - RSA.validate(params, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + try RSA.validate(params, key_usages); log.warn(.not_implemented, "generateKey", .{ .name = params.name }); }, .name => |js_name| return generateKeyFromName(try js_name.toSSO(false), extractable, key_usages, exec), .object => |object| return generateKeyFromName(try object.name.toSSO(false), extractable, key_usages, exec), - .invalid => return local.rejectPromise(.{ .type_error = "invalid algorithm" }), + .invalid => return local.typeError("invalid algorithm"), } - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } fn generateKeyFromName( @@ -77,17 +75,6 @@ fn generateKeyFromName( extractable: bool, key_usages: []const []const u8, exec: *const Execution, -) !js.Promise { - return _generateKeyFromName(name, extractable, key_usages, exec) catch |err| { - return exec.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; -} - -fn _generateKeyFromName( - name: String, - extractable: bool, - key_usages: []const []const u8, - exec: *const Execution, ) !js.Promise { if (name.eql(comptime .wrap("X25519"))) { return X25519.init(extractable, key_usages, exec); @@ -150,18 +137,16 @@ pub fn importKey( const is_private = importKind(format, key_data); if (asymmetricAllowedUsages(name, is_private)) |allowed| { // Public keys may have empty usages; secret/private keys may not. - const mask = common.usageMaskInner(allowed, key_usages, is_private) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + const mask = try common.usageMaskInner(allowed, key_usages, is_private); if (EC.canonicalName(name) != null) { const der = switch (key_data) { .bytes => |b| b.values, - .jwk => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }), + .jwk => return error.NotSupported, }; return EC.import(name, algo.namedCurve(), format, der, is_private, extractable, mask, exec); } log.warn(.not_implemented, "SubtleCrypto.importKey", .{ .name = name }); - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } // Resolve the raw key bytes from the requested format. Symmetric keys @@ -171,27 +156,22 @@ pub fn importKey( break :blk switch (key_data) { .bytes => |b| b.values, // A JWK object passed where a BufferSource is expected. - .jwk => return local.rejectPromise(.{ .type_error = "raw format expects a BufferSource" }), + .jwk => return local.typeError("raw format expects a BufferSource"), }; } if (std.mem.eql(u8, format, "jwk")) { const jwk = switch (key_data) { .jwk => |j| j, - .bytes => return local.rejectPromise(.{ .type_error = "jwk format expects an object" }), + .bytes => return local.typeError("jwk format expects an object"), }; if (!std.mem.eql(u8, jwk.kty, "oct")) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; } - const k = jwk.k orelse { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); - }; - break :blk common.base64Decode(exec.local_arena, k) catch |err| switch (err) { - error.DataError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }), - else => |e| return e, - }; + const k = jwk.k orelse return error.DataError; + break :blk try common.base64Decode(exec.local_arena, k); } // spki / pkcs8 (asymmetric formats) are not supported for these algorithms. - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }; if (AES.canonicalName(name) != null) { @@ -202,9 +182,7 @@ pub fn importKey( // no length constraint and these keys are non-extractable. inline for ([_][]const u8{ "HKDF", "PBKDF2" }) |derive_name| { if (eqlIgnoreCase(name, derive_name)) { - const mask = common.usageMask(&.{ "deriveKey", "deriveBits" }, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + const mask = try common.usageMask(&.{ "deriveKey", "deriveBits" }, key_usages); const crypto_key = try CryptoKey.init(exec, .{ ._type = .derive, ._kind = .secret, @@ -222,13 +200,13 @@ pub fn importKey( .string => |s| s, .object => |o| o.name, }, - else => return local.rejectPromise(.{ .type_error = "HMAC import requires a hash" }), + else => return local.typeError("HMAC import requires a hash"), }; return HMAC.import(hash_name, raw, extractable, key_usages, exec); } log.warn(.not_implemented, "SubtleCrypto.importKey", .{ .name = name }); - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } /// Whether the requested format/key-data describe a private key. The format @@ -274,7 +252,7 @@ pub fn exportKey( ) !js.Promise { const local = exec.js.local.?; if (!key.canExportKey()) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); + return error.InvalidAccessError; } if (std.mem.eql(u8, format, "raw")) { @@ -288,10 +266,10 @@ pub fn exportKey( const is_unsupported = std.mem.eql(u8, format, "pkcs8") or std.mem.eql(u8, format, "spki"); if (is_unsupported) { log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = format }); - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } - return local.rejectPromise(.{ .type_error = "invalid format" }); + return local.typeError("invalid format"); } /// The JSON Web Key returned for symmetric ("oct") keys. @@ -318,7 +296,7 @@ fn exportJwk(key: *CryptoKey, exec: *const Execution) !js.Promise { }, else => { log.warn(.not_implemented, "SubtleCrypto.exportKey", .{ .format = "jwk", .type = key._type }); - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }, }; @@ -340,9 +318,7 @@ pub fn deriveBits( exec: *const Execution, ) !js.Promise { const local = exec.js.local.?; - const bits = deriveRaw(algo, base_key, length, base_key.canDeriveBits(), exec) catch |err| { - return rejectDerive(local, err); - }; + const bits = try deriveRaw(algo, base_key, length, base_key.canDeriveBits(), exec); return local.resolvePromise(js.ArrayBuffer{ .values = bits }); } @@ -357,35 +333,28 @@ pub fn deriveKey( key_usages: []const []const u8, exec: *const Execution, ) !js.Promise { - const local = exec.js.local.?; // The base key's deriveKey usage (not deriveBits) gates this operation. const usage_ok = base_key.canDeriveKey(); switch (derived) { .keyed => |k| { if (AES.canonicalName(k.name) == null) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } - const bits = deriveRaw(algo, base_key, k.length, usage_ok, exec) catch |err| { - return rejectDerive(local, err); - }; + const bits = try deriveRaw(algo, base_key, k.length, usage_ok, exec); return AES.import(k.name, bits, extractable, key_usages, exec); }, .hmac => |h| { const hash_name = h.hash.name(); - const hash_md = crypto.findDigest(hash_name) catch { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); - }; + const hash_md = crypto.findDigest(hash_name) catch return error.NotSupported; // Default length, per spec, is the hash's block size (in bits). const length: u32 = h.length orelse @intCast(crypto.EVP_MD_block_size(hash_md) * 8); - const bits = deriveRaw(algo, base_key, length, usage_ok, exec) catch |err| { - return rejectDerive(local, err); - }; + const bits = try deriveRaw(algo, base_key, length, usage_ok, exec); return HMAC.import(hash_name, bits, extractable, key_usages, exec); }, .object, .name => { log.warn(.not_implemented, "SubtleCrypto.deriveKey", .{}); - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }, } } @@ -426,16 +395,6 @@ fn deriveRaw( } } -/// Maps a KDF error to the spec-mandated DOMException (OutOfMemory propagates). -fn rejectDerive(local: *const js.Local, err: KDF.Error) !js.Promise { - return switch (err) { - error.InvalidAccessError => local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }), - error.NotSupported => local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }), - error.OperationError => local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } }), - error.OutOfMemory => error.OutOfMemory, - }; -} - /// Encrypts data with the given key and algorithm. pub fn encrypt( _: *const SubtleCrypto, @@ -469,15 +428,10 @@ fn cryptOp( const params = switch (algo) { .params => |p| p, // A bare string identifier carries no iv/counter, so it can't drive AES. - .name => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }), + .name => return error.NotSupported, }; - const out = AES.crypt(params, key, data, encrypting, exec) catch |err| switch (err) { - error.InvalidAccessError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }), - error.OperationError => return local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } }), - error.NotSupported => return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }), - error.OutOfMemory => return error.OutOfMemory, - }; + const out = try AES.crypt(params, key, data, encrypting, exec); return local.resolvePromise(js.ArrayBuffer{ .values = out }); } @@ -495,7 +449,7 @@ pub fn sign( .hmac => return HMAC.sign(algo, key, data, exec), else => { log.warn(.not_implemented, "SubtleCrypto.sign", .{ .key_type = key._type }); - return exec.js.local.?.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); + return error.InvalidAccessError; }, }; } @@ -509,14 +463,13 @@ pub fn verify( data: []const u8, // ArrayBuffer. exec: *const Execution, ) !js.Promise { - const local = exec.js.local.?; if (!algo.isHMAC()) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }); + return error.InvalidAccessError; } return switch (key._type) { .hmac => HMAC.verify(key, signature, data, exec), - else => local.rejectPromise(.{ .dom_exception = .{ .err = error.InvalidAccessError } }), + else => error.InvalidAccessError, }; } @@ -540,16 +493,14 @@ pub fn digest(_: *const SubtleCrypto, algo: DigestInput, data: js.TypedArray(u8) const local = exec.js.local.?; const algo_name = algo.name() orelse { - return local.rejectPromise(.{ .type_error = "required member name is undefined" }); + return local.typeError("required member name is undefined"); }; if (algo_name.len > 10) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } const normalized = std.ascii.upperString(exec.buf, algo_name); - const digest_type = crypto.findDigest(normalized) catch { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); - }; + const digest_type = crypto.findDigest(normalized) catch return error.NotSupported; const bytes = data.values; const out = exec.buf[0..crypto.EVP_MAX_MD_SIZE]; diff --git a/src/browser/webapi/crypto/AES.zig b/src/browser/webapi/crypto/AES.zig index 63005f0c0f..3cb68142ec 100644 --- a/src/browser/webapi/crypto/AES.zig +++ b/src/browser/webapi/crypto/AES.zig @@ -85,9 +85,7 @@ pub fn generate( exec: *const Execution, ) !js.Promise { const local = exec.js.local.?; - validate(params, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + try validate(params, key_usages); // validate() already confirmed the usages and length are well-formed. const allowed = allowedUsages(params.name).?; @@ -122,15 +120,13 @@ pub fn import( const local = exec.js.local.?; const canonical = canonicalName(name) orelse { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }; - const mask = common.usageMask(allowedUsages(name).?, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + const mask = try common.usageMask(allowedUsages(name).?, key_usages); if (raw.len != 16 and raw.len != 24 and raw.len != 32) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; } const crypto_key = try CryptoKey.init(exec, .{ diff --git a/src/browser/webapi/crypto/EC.zig b/src/browser/webapi/crypto/EC.zig index 6e249e2229..73bc5d392c 100644 --- a/src/browser/webapi/crypto/EC.zig +++ b/src/browser/webapi/crypto/EC.zig @@ -94,9 +94,7 @@ pub fn generate( exec: *const Execution, ) !js.Promise { const local = exec.js.local.?; - validate(params, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + try validate(params, key_usages); const name = canonicalName(params.name).?; const curve = curveCanonical(params.namedCurve).?; @@ -111,7 +109,7 @@ pub fn generate( const ec = crypto.EC_KEY_new_by_curve_name(nid) orelse return error.OutOfMemory; defer crypto.EC_KEY_free(ec); if (crypto.EC_KEY_generate_key(ec) != 1) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.OperationError } }); + return error.OperationError; } const private_pkey = crypto.EVP_PKEY_new() orelse return error.OutOfMemory; @@ -167,28 +165,28 @@ pub fn import( const canonical = canonicalName(name).?; const curve = curveCanonical(named_curve) orelse { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }; var ptr: [*c]const u8 = der.ptr; const pkey: *crypto.EVP_PKEY = blk: { if (std.mem.eql(u8, format, "spki")) { break :blk crypto.d2i_PUBKEY(null, &ptr, @intCast(der.len)) orelse { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; }; } if (std.mem.eql(u8, format, "pkcs8")) { break :blk crypto.d2i_AutoPrivateKey(null, &ptr, @intCast(der.len)) orelse { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; }; } // jwk / raw not implemented yet. - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; }; errdefer crypto.EVP_PKEY_free(pkey); if (crypto.EVP_PKEY_id(pkey) != crypto.EVP_PKEY_EC) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; } const crypto_key = try CryptoKey.init(exec, .{ diff --git a/src/browser/webapi/crypto/HMAC.zig b/src/browser/webapi/crypto/HMAC.zig index 19f6d51c6b..882a977adb 100644 --- a/src/browser/webapi/crypto/HMAC.zig +++ b/src/browser/webapi/crypto/HMAC.zig @@ -40,7 +40,7 @@ pub fn init( // The union probe match to get here is pretty simple, so we can end up here // for an unknown/invalid algo. if (!std.ascii.eqlIgnoreCase(params.name, "HMAC")) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.NotSupported } }); + return error.NotSupported; } const hash_name = switch (params.hash) { .string => |str| str, @@ -48,9 +48,7 @@ pub fn init( }; // Per spec, an unrecognized hash is caught during algorithm normalization // and surfaces as NotSupportedError. - const digest = crypto.findDigest(hash_name) catch return local.rejectPromise(.{ - .dom_exception = .{ .err = error.NotSupported }, - }); + const digest = crypto.findDigest(hash_name) catch return error.NotSupported; // HMAC only accepts sign / verify; any other usage is a SyntaxError per // the spec, even when the entry exists elsewhere in CryptoKey.Usages. @@ -61,15 +59,11 @@ pub fn init( } else if (std.mem.eql(u8, usage, "verify")) { mask |= CryptoKey.Usages.verify; } else { - return local.rejectPromise(.{ - .dom_exception = .{ .err = error.SyntaxError }, - }); + return error.SyntaxError; } } if (key_usages.len == 0) { - return local.rejectPromise(.{ - .dom_exception = .{ .err = error.SyntaxError }, - }); + return error.SyntaxError; } const block_size: usize = blk: { @@ -115,16 +109,12 @@ pub fn import( ) !js.Promise { const local = exec.js.local.?; - const digest = crypto.findDigest(hash_name) catch return local.rejectPromise(.{ - .dom_exception = .{ .err = error.NotSupported }, - }); + const digest = crypto.findDigest(hash_name) catch return error.NotSupported; - const mask = common.usageMask(&.{ "sign", "verify" }, key_usages) catch |err| { - return local.rejectPromise(.{ .dom_exception = .{ .err = err } }); - }; + const mask = try common.usageMask(&.{ "sign", "verify" }, key_usages); if (raw.len == 0) { - return local.rejectPromise(.{ .dom_exception = .{ .err = error.DataError } }); + return error.DataError; } const crypto_key = try CryptoKey.init(exec, .{ diff --git a/src/browser/webapi/crypto/X25519.zig b/src/browser/webapi/crypto/X25519.zig index adaa09bf70..8ada529395 100644 --- a/src/browser/webapi/crypto/X25519.zig +++ b/src/browser/webapi/crypto/X25519.zig @@ -42,9 +42,7 @@ pub fn init( // Calculate usages; only matters for private key. // Only deriveKey() and deriveBits() be used for X25519. if (key_usages.len == 0) { - return local.rejectPromise(.{ - .dom_exception = .{ .err = error.SyntaxError }, - }); + return error.SyntaxError; } var mask: u8 = 0; iter_usages: for (key_usages) |usage| { @@ -55,9 +53,7 @@ pub fn init( } } // Unknown usage if got here. - return local.rejectPromise(.{ - .dom_exception = .{ .err = error.SyntaxError }, - }); + return error.SyntaxError; } const public_value = try exec.local_arena.alloc(u8, crypto.X25519_PUBLIC_VALUE_LEN); diff --git a/src/browser/webapi/net/Request.zig b/src/browser/webapi/net/Request.zig index 18e8d1ff36..58b44d56dd 100644 --- a/src/browser/webapi/net/Request.zig +++ b/src/browser/webapi/net/Request.zig @@ -237,24 +237,21 @@ pub fn getBodyUsed(self: *const Request) bool { return self._body_used; } -// Marks a present body consumed; returns a rejected promise if it already was. -fn consume(self: *Request, local: *const js.Local) ?js.Promise { +// Marks a present body consumed; a TypeError if it already was. +fn consume(self: *Request, local: *const js.Local) !void { if (self._body == null) { - return null; + return; } if (self._body_used) { - return local.rejectPromise(.{ .type_error = "Body has already been read" }); + return local.typeError("Body has already been read"); } self._body_used = true; - return null; } pub fn blob(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); const body = self._body orelse ""; const headers = try self.getHeaders(exec); @@ -266,17 +263,13 @@ pub fn blob(self: *Request, exec: *const Execution) !js.Promise { pub fn text(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); return local.resolvePromise(body_init.stripUtf8Bom(self._body orelse "")); } pub fn json(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); const value = local.parseJSON(body_init.stripUtf8Bom(self._body orelse "")) catch { return local.rejectPromise(.{ .syntax_error = "failed to parse" }); @@ -286,32 +279,26 @@ pub fn json(self: *Request, exec: *const Execution) !js.Promise { pub fn arrayBuffer(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); return local.resolvePromise(js.ArrayBuffer{ .values = self._body orelse "" }); } pub fn bytes(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); return local.resolvePromise(js.TypedArray(u8){ .values = self._body orelse "" }); } pub fn formData(self: *Request, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); // Per Fetch, a null body acts as an empty byte sequence. const body = self._body orelse ""; const headers = try self.getHeaders(exec); const content_type = try headers.get("content-type", exec) orelse { - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); }; var it = ContentTypeIterator.init(content_type); const essence = it.essence; @@ -322,12 +309,12 @@ pub fn formData(self: *Request, exec: *const Execution) !js.Promise { if (std.ascii.eqlIgnoreCase(essence, "multipart/form-data")) { const boundary = it.findBoundary(); if (boundary.len == 0) { - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); } const form_data = FormData.initFromMultipart(body, boundary, exec) catch |err| switch (err) { error.OutOfMemory => return err, - else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }), + else => return local.typeError("Failed to parse body as FormData"), }; return local.resolvePromise(form_data); } @@ -335,12 +322,12 @@ pub fn formData(self: *Request, exec: *const Execution) !js.Promise { if (std.ascii.eqlIgnoreCase(essence, "application/x-www-form-urlencoded")) { const form_data = FormData.initFromUrlEncoded(body, exec) catch |err| switch (err) { error.OutOfMemory => return err, - else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }), + else => return local.typeError("Failed to parse body as FormData"), }; return local.resolvePromise(form_data); } - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); } pub fn clone(self: *const Request, exec: *const Execution) !*Request { diff --git a/src/browser/webapi/net/Response.zig b/src/browser/webapi/net/Response.zig index b3343a2a65..50d3f4a09b 100644 --- a/src/browser/webapi/net/Response.zig +++ b/src/browser/webapi/net/Response.zig @@ -292,43 +292,38 @@ pub fn getBodyUsed(self: *const Response) bool { }; } -// Marks a present body consumed; returns a rejected promise if it already was. -fn consume(self: *Response, local: *const js.Local) ?js.Promise { +// Marks a present body consumed; a TypeError if it already was. +fn consume(self: *Response, local: *const js.Local) !void { switch (self._body) { - .empty => return null, + .empty => return, else => {}, } if (self._body_used) { - return local.rejectPromise(.{ .type_error = "Body has already been read" }); + return local.typeError("Body has already been read"); } self._body_used = true; - return null; } pub fn getText(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); const body = switch (self._body) { .bytes => |b| body_init.stripUtf8Bom(b), .empty => "", - .stream => return local.rejectPromise(.{ .type_error = "Cannot read text from stream body" }), + .stream => return local.typeError("Cannot read text from stream body"), }; return local.resolvePromise(body); } pub fn getJson(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); const body = switch (self._body) { .bytes => |b| body_init.stripUtf8Bom(b), .empty => "", - .stream => return local.rejectPromise(.{ .type_error = "Cannot read JSON from stream body" }), + .stream => return local.typeError("Cannot read JSON from stream body"), }; const value = local.parseJSON(body) catch { return local.rejectPromise(.{ .syntax_error = "failed to parse" }); @@ -338,9 +333,7 @@ pub fn getJson(self: *Response, exec: *const Execution) !js.Promise { pub fn arrayBuffer(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| { - return rejected; - } + try self.consume(local); return switch (self._body) { .bytes => |body| local.resolvePromise(js.ArrayBuffer{ .values = body }), @@ -464,11 +457,11 @@ const StreamConsumer = struct { pub fn blob(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| return rejected; + try self.consume(local); const body = switch (self._body) { .bytes => |b| b, .empty => "", - .stream => return local.rejectPromise(.{ .type_error = "Cannot read blob from stream body" }), + .stream => return local.typeError("Cannot read blob from stream body"), }; const content_type = try self._headers.get("content-type", exec) orelse ""; const b = try Blob.initFromBytes(body, content_type, exec); @@ -477,26 +470,26 @@ pub fn blob(self: *Response, exec: *const Execution) !js.Promise { pub fn bytes(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| return rejected; + try self.consume(local); const body = switch (self._body) { .bytes => |b| b, .empty => "", - .stream => return local.rejectPromise(.{ .type_error = "Cannot read bytes from stream body" }), + .stream => return local.typeError("Cannot read bytes from stream body"), }; return local.resolvePromise(js.TypedArray(u8){ .values = body }); } pub fn formData(self: *Response, exec: *const Execution) !js.Promise { const local = exec.js.local.?; - if (self.consume(local)) |rejected| return rejected; + try self.consume(local); const body = switch (self._body) { .bytes => |b| b, .empty => "", - .stream => return local.rejectPromise(.{ .type_error = "Cannot read FormData from stream body" }), + .stream => return local.typeError("Cannot read FormData from stream body"), }; const content_type = try self._headers.get("content-type", exec) orelse { - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); }; var it = ContentTypeIterator.init(content_type); const essence = it.essence; @@ -507,12 +500,12 @@ pub fn formData(self: *Response, exec: *const Execution) !js.Promise { if (std.ascii.eqlIgnoreCase(essence, "multipart/form-data")) { const boundary = it.findBoundary(); if (boundary.len == 0) { - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); } const form_data = FormData.initFromMultipart(body, boundary, exec) catch |err| switch (err) { error.OutOfMemory => return err, - else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }), + else => return local.typeError("Failed to parse body as FormData"), }; return local.resolvePromise(form_data); } @@ -520,12 +513,12 @@ pub fn formData(self: *Response, exec: *const Execution) !js.Promise { if (std.ascii.eqlIgnoreCase(essence, "application/x-www-form-urlencoded")) { const form_data = FormData.initFromUrlEncoded(body, exec) catch |err| switch (err) { error.OutOfMemory => return err, - else => return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }), + else => return local.typeError("Failed to parse body as FormData"), }; return local.resolvePromise(form_data); } - return local.rejectPromise(.{ .type_error = "Failed to parse body as FormData" }); + return local.typeError("Failed to parse body as FormData"); } pub fn clone(self: *const Response, exec: *const Execution) !*Response { diff --git a/src/browser/webapi/storage/idb/IDBFactory.zig b/src/browser/webapi/storage/idb/IDBFactory.zig index b520acd53a..531a280a54 100644 --- a/src/browser/webapi/storage/idb/IDBFactory.zig +++ b/src/browser/webapi/storage/idb/IDBFactory.zig @@ -389,7 +389,7 @@ const DeleteContext = struct { pub fn databases(_: *IDBFactory, exec: *Execution) !js.Promise { const local = exec.js.local.?; // unavailable for opaque origins, e.g. about:blank - const origin = exec.origin() orelse return local.rejectPromise(.{ .dom_exception = .{ .err = error.SecurityError } }); + const origin = exec.origin() orelse return error.SecurityError; const engine = try exec.session.idb.engineForOrigin(origin); return local.resolvePromise(try engine.databases(exec.call_arena)); }