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)); }