From c84106570fcde837e0be43591cc2321b5c75f54f Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Thu, 29 Jan 2026 14:21:24 +0800 Subject: [PATCH] Cleanup js -> string Converting a JS value to a string is a bit messy right now. There's duplication between string helpers in js.Local, and what js.String and js.Value provide. Now, all stringifying functions are in js.String, with some helpers in js.Value. Also tried to streamline the APIs around most common-cases (e.g. js.String -> []u8 using call_arena). js.String now also implements format, so it can be used as-is in some cases. --- src/browser/Page.zig | 1 - src/browser/js/Caller.zig | 8 +- src/browser/js/Context.zig | 20 ++- src/browser/js/Env.zig | 3 +- src/browser/js/Local.zig | 150 ++++--------------- src/browser/js/Module.zig | 4 +- src/browser/js/Object.zig | 6 +- src/browser/js/String.zig | 92 +++++++++--- src/browser/js/TryCatch.zig | 4 +- src/browser/js/Value.zig | 68 +++++---- src/browser/js/bridge.zig | 2 +- src/browser/webapi/AbortSignal.zig | 2 +- src/browser/webapi/Console.zig | 2 +- src/browser/webapi/CustomElementRegistry.zig | 5 +- src/browser/webapi/KeyValueList.zig | 3 +- src/browser/webapi/Window.zig | 2 +- src/browser/webapi/net/URLSearchParams.zig | 4 +- src/main_wpt.zig | 2 +- 18 files changed, 173 insertions(+), 205 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 4c6b6ea8..81749822 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -276,7 +276,6 @@ fn reset(self: *Page, comptime initializing: bool) !void { self._arena_pool_leak_track.clearRetainingCapacity(); } - // We force a garbage collection between page navigations to keep v8 // memory usage as low as possible. self._session.browser.env.memoryPressureNotification(.moderate); diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index 1eb160ba..d1400627 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -314,14 +314,14 @@ fn isInErrorSet(err: anyerror, comptime T: type) bool { } fn nameToString(self: *const Caller, comptime T: type, name: *const v8.Name) !T { - const v8_string = @as(*const v8.String, @ptrCast(name)); + const handle = @as(*const v8.String, @ptrCast(name)); if (T == string.String) { - return self.local.jsStringToStringSSO(v8_string, .{}); + return js.String.toSSO(.{ .local = &self.local, .handle = handle }, false); } if (T == string.Global) { - return self.local.jsStringToStringSSO(v8_string, .{ .allocator = self.local.ctx.allocator }); + return js.String.toSSO(.{ .local = &self.local, .handle = handle }, true); } - return try self.local.valueHandleToString(v8_string, .{}); + return try js.String.toSlice(.{ .local = &self.local, .handle = handle }); } fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void { diff --git a/src/browser/js/Context.zig b/src/browser/js/Context.zig index 648f6de3..03c9ff7b 100644 --- a/src/browser/js/Context.zig +++ b/src/browser/js/Context.zig @@ -357,9 +357,13 @@ pub fn module(self: *Context, comptime want_result: bool, local: *const js.Local // Some module-loading errors aren't handled by TryCatch. We need to // get the error from the module itself. + const message = blk: { + const e = mod.getException().toString() catch break :blk "???"; + break :blk e.toSlice() catch "???"; + }; log.warn(.js, "evaluate module", .{ + .message = message, .specifier = owned_url, - .message = mod.getException().toString(.{}) catch "???", }); return error.EvaluationError; }; @@ -457,11 +461,11 @@ fn postCompileModule(self: *Context, mod: js.Module, url: [:0]const u8, local: * const request_len = requests.len(); const script_manager = self.script_manager.?; for (0..request_len) |i| { - const specifier = try local.jsStringToZigZ(requests.get(i).specifier(), .{}); + const specifier = requests.get(i).specifier(local); const normalized_specifier = try script_manager.resolveSpecifier( self.call_arena, url, - specifier, + try specifier.toSliceZ(), ); const nested_gop = try self.module_cache.getOrPut(self.arena, normalized_specifier); if (!nested_gop.found_existing) { @@ -494,14 +498,14 @@ fn resolveModuleCallback( _ = import_attributes; const self = fromC(c_context.?); - var local = js.Local{ + const local = js.Local{ .ctx = self, .handle = c_context.?, .isolate = self.isolate, .call_arena = self.call_arena, }; - const specifier = local.jsStringToZigZ(c_specifier.?, .{}) catch |err| { + const specifier = js.String.toSliceZ(.{ .local = &local, .handle = c_specifier.? }) catch |err| { log.err(.js, "resolve module", .{ .err = err }); return null; }; @@ -527,19 +531,19 @@ pub fn dynamicModuleCallback( _ = import_attrs; const self = fromC(c_context.?); - var local = js.Local{ + const local = js.Local{ .ctx = self, .handle = c_context.?, .call_arena = self.call_arena, .isolate = self.isolate, }; - const resource = local.jsStringToZigZ(resource_name.?, .{}) catch |err| { + const resource = 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); }; - const specifier = local.jsStringToZigZ(v8_specifier.?, .{}) catch |err| { + 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); }; diff --git a/src/browser/js/Env.zig b/src/browser/js/Env.zig index 69f7cd20..9936e63d 100644 --- a/src/browser/js/Env.zig +++ b/src/browser/js/Env.zig @@ -254,8 +254,7 @@ fn promiseRejectCallback(message_handle: v8.PromiseRejectMessage) callconv(.c) v const value = if (v8.v8__PromiseRejectMessage__GetValue(&message_handle)) |v8_value| - // @HandleScope - no reason to create a js.Context here - local.valueHandleToString(v8_value, .{}) catch |err| @errorName(err) + js.Value.toStringSlice(.{ .local = &local, .handle = v8_value }) catch |err| @errorName(err) else "no value"; diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index c47a71a7..2d20e7ab 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -473,10 +473,10 @@ pub fn jsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !T { if (ptr.child == u8) { if (ptr.sentinel()) |s| { if (comptime s == 0) { - return self.valueToStringZ(js_val, .{}); + return try js_val.toStringSliceZ(); } } else { - return self.valueToString(js_val, .{}); + return try js_val.toStringSlice(); } } @@ -549,10 +549,8 @@ pub fn jsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !T { }, .@"enum" => |e| { if (@hasDecl(T, "js_enum_from_string")) { - if (!js_val.isString()) { - return error.InvalidArgument; - } - return std.meta.stringToEnum(T, try self.valueToString(js_val, .{})) orelse return error.InvalidArgument; + const js_str = js_val.isString() orelse return error.InvalidArgument; + return std.meta.stringToEnum(T, try js_str.toSlice()) orelse return error.InvalidArgument; } switch (@typeInfo(e.tag_type)) { .int => return std.meta.intToEnum(T, try jsIntToZig(e.tag_type, js_val)), @@ -625,17 +623,12 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T { return try promise.persist(); }, string.String => { - if (!js_val.isString()) { - return null; - } - return try self.valueToStringSSO(js_val, .{ .allocator = self.ctx.call_arena }); + const js_str = js_val.isString() orelse return null; + return try js_str.toSSO(false); }, string.Global => { - if (!js_val.isString()) { - return null; - } - // Use arena for persistent strings - return .{ .str = try self.valueToStringSSO(js_val, .{ .allocator = self.ctx.arena }) }; + const js_str = js_val.isString() orelse return null; + return try js_str.toSSO(true); }, else => { if (!js_val.isObject()) { @@ -883,7 +876,7 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr } if (ptr.child == u8) { - if (js_val.isString()) { + if (v8.v8__Value__IsString(js_val.handle)) { return .{ .ok = {} }; } // anything can be coerced into a string @@ -931,10 +924,11 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr if (js_arr.len() == arr.len) { return .{ .ok = {} }; } - } else if (js_val.isString() and arr.child == u8) { - const str = try js_val.toString(self.local); - if (str.lenUtf8(self.isolate) == arr.len) { - return .{ .ok = {} }; + } else if (arr.child == u8) { + if (js_val.isString()) |js_str| { + if (js_str.lenUtf8(self.isolate) == arr.len) { + return .{ .ok = {} }; + } } } return .{ .invalid = {} }; @@ -947,7 +941,7 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr .@"struct" => { // Handle string.String and string.Global specially if (T == string.String or T == string.Global) { - if (js_val.isString()) { + if (v8.v8__Value__IsString(js_val.handle)) { return .{ .ok = {} }; } // Anything can be coerced to a string @@ -1080,14 +1074,15 @@ pub fn stackTrace(self: *const Local) !?[]const u8 { const frame_count = v8.v8__StackTrace__GetFrameCount(stack_trace_handle); if (v8.v8__StackTrace__CurrentScriptNameOrSourceURL__STATIC(isolate.handle)) |script| { - try writer.print("{s}<{s}>", .{ separator, try self.jsStringToZig(script, .{}) }); + const stack = js.String{ .local = self, .handle = script }; + try writer.print("{s}<{f}>", .{ separator, stack }); } for (0..@intCast(frame_count)) |i| { const frame_handle = v8.v8__StackTrace__GetFrame(stack_trace_handle, isolate.handle, @intCast(i)).?; if (v8.v8__StackFrame__GetFunctionName(frame_handle)) |name| { - const script = try self.jsStringToZig(name, .{}); - try writer.print("{s}{s}:{d}", .{ separator, script, v8.v8__StackFrame__GetLineNumber(frame_handle) }); + const script = js.String{ .local = self, .handle = name }; + try writer.print("{s}{f}:{d}", .{ separator, script, v8.v8__StackFrame__GetLineNumber(frame_handle) }); } else { try writer.print("{s}:{d}", .{ separator, v8.v8__StackFrame__GetLineNumber(frame_handle) }); } @@ -1095,100 +1090,6 @@ pub fn stackTrace(self: *const Local) !?[]const u8 { return buf.items; } -// == Stringifiers == -const ToStringOpts = struct { - allocator: ?Allocator = null, -}; -pub fn valueToString(self: *const Local, js_val: js.Value, opts: ToStringOpts) ![]u8 { - return self.valueHandleToString(js_val.handle, opts); -} -pub fn valueToStringZ(self: *const Local, js_val: js.Value, opts: ToStringOpts) ![:0]u8 { - return self.valueHandleToStringZ(js_val.handle, opts); -} - -pub fn valueHandleToString(self: *const Local, js_val: *const v8.Value, opts: ToStringOpts) ![]u8 { - return self._valueToString(false, js_val, opts); -} -pub fn valueHandleToStringZ(self: *const Local, js_val: *const v8.Value, opts: ToStringOpts) ![:0]u8 { - return self._valueToString(true, js_val, opts); -} - -fn _valueToString(self: *const Local, comptime null_terminate: bool, value_handle: *const v8.Value, opts: ToStringOpts) !(if (null_terminate) [:0]u8 else []u8) { - var resolved_value_handle = value_handle; - if (v8.v8__Value__IsSymbol(value_handle)) { - const symbol_handle = v8.v8__Symbol__Description(@ptrCast(value_handle), self.isolate.handle).?; - resolved_value_handle = @ptrCast(symbol_handle); - } - - const string_handle = v8.v8__Value__ToString(resolved_value_handle, self.handle) orelse { - return error.JsException; - }; - - return self._jsStringToZig(null_terminate, string_handle, opts); -} - -pub fn jsStringToZig(self: *const Local, str: anytype, opts: ToStringOpts) ![]u8 { - return self._jsStringToZig(false, str, opts); -} -pub fn jsStringToZigZ(self: *const Local, str: anytype, opts: ToStringOpts) ![:0]u8 { - return self._jsStringToZig(true, str, opts); -} -fn _jsStringToZig(self: *const Local, comptime null_terminate: bool, str: anytype, opts: ToStringOpts) !(if (null_terminate) [:0]u8 else []u8) { - const handle = if (@TypeOf(str) == js.String) str.handle else str; - - const len = v8.v8__String__Utf8Length(handle, self.isolate.handle); - const allocator = opts.allocator orelse self.call_arena; - const buf = try (if (comptime null_terminate) allocator.allocSentinel(u8, @intCast(len), 0) else allocator.alloc(u8, @intCast(len))); - const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); - std.debug.assert(n == len); - - return buf; -} - -// Convert JS string to string.String with SSO -pub fn valueToStringSSO(self: *const Local, js_val: js.Value, opts: ToStringOpts) !string.String { - const string_handle = v8.v8__Value__ToString(js_val.handle, self.handle) orelse { - return error.JsException; - }; - return self.jsStringToStringSSO(string_handle, opts); -} - -pub fn jsStringToStringSSO(self: *const Local, str: anytype, opts: ToStringOpts) !string.String { - const handle = if (@TypeOf(str) == js.String) str.handle else str; - const len: usize = @intCast(v8.v8__String__Utf8Length(handle, self.isolate.handle)); - - if (len <= 12) { - var content: [12]u8 = undefined; - const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, &content[0], content.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); - if (comptime IS_DEBUG) { - std.debug.assert(n == len); - } - // Weird that we do this _after_, but we have to..I've seen weird issues - // in ReleaseMode where v8 won't write to content if it starts off zero - // initiated - @memset(content[len..], 0); - return .{ .len = @intCast(len), .payload = .{ .content = content } }; - } - - const allocator = opts.allocator orelse self.call_arena; - const buf = try allocator.alloc(u8, len); - const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); - if (comptime IS_DEBUG) { - std.debug.assert(n == len); - } - - var prefix: [4]u8 = @splat(0); - @memcpy(&prefix, buf[0..4]); - - return .{ - .len = @intCast(len), - .payload = .{ .heap = .{ - .prefix = prefix, - .ptr = buf.ptr, - } }, - }; -} - // == Promise Helpers == pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise { var resolver = js.PromiseResolver.init(self); @@ -1233,18 +1134,16 @@ fn _debugValue(self: *const Local, js_val: js.Value, seen: *std.AutoHashMapUnman if (js_val.isSymbol()) { const symbol_handle = v8.v8__Symbol__Description(@ptrCast(js_val.handle), self.isolate.handle).?; - const js_sym_str = try self.valueToString(.{ .local = self, .handle = symbol_handle }, .{}); - return writer.print("{s} (symbol)", .{js_sym_str}); + return writer.print("{f} (symbol)", .{js.String{ .local = self, .handle = @ptrCast(symbol_handle) }}); } - const js_type = try self.jsStringToZig(js_val.typeOf(), .{}); - const js_val_str = try self.valueToString(js_val, .{}); + const js_val_str = try js_val.toStringSlice(); if (js_val_str.len > 2000) { try writer.writeAll(js_val_str[0..2000]); try writer.writeAll(" ... (truncated)"); } else { try writer.writeAll(js_val_str); } - return writer.print(" ({s})", .{js_type}); + return writer.print(" ({f})", .{js_val.typeOf()}); } const js_obj = js_val.toObject(); @@ -1266,7 +1165,7 @@ fn _debugValue(self: *const Local, js_val: js.Value, seen: *std.AutoHashMapUnman } const own_len = js_obj.getOwnPropertyNames().len(); if (own_len == 0) { - const js_val_str = try self.valueToString(js_val, .{}); + const js_val_str = try js_val.toStringSlice(); if (js_val_str.len > 2000) { try writer.writeAll(js_val_str[0..2000]); return writer.writeAll(" ... (truncated)"); @@ -1281,10 +1180,11 @@ fn _debugValue(self: *const Local, js_val: js.Value, seen: *std.AutoHashMapUnman try writer.writeByte('\n'); } const field_name = try names_arr.get(@intCast(i)); - const name = try self.valueToString(field_name, .{}); + const name = try field_name.toStringSlice(); try writer.splatByteAll(' ', depth); try writer.writeAll(name); try writer.writeAll(": "); + const field_val = try js_obj.get(name); try self._debugValue(field_val, seen, depth + 1, writer); if (i != len - 1) { diff --git a/src/browser/js/Module.zig b/src/browser/js/Module.zig index c77c46fa..1e290d2d 100644 --- a/src/browser/js/Module.zig +++ b/src/browser/js/Module.zig @@ -131,7 +131,7 @@ const Requests = struct { const Request = struct { handle: *const v8.ModuleRequest, - pub fn specifier(self: Request) *const v8.String { - return v8.v8__ModuleRequest__GetSpecifier(self.handle).?; + pub fn specifier(self: Request, local: *const js.Local) js.String { + return .{ .local = local, .handle = v8.v8__ModuleRequest__GetSpecifier(self.handle).? }; } }; diff --git a/src/browser/js/Object.zig b/src/browser/js/Object.zig index 1306234c..4a4bc216 100644 --- a/src/browser/js/Object.zig +++ b/src/browser/js/Object.zig @@ -201,8 +201,8 @@ pub const NameIterator = struct { } self.idx += 1; - const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), self.local.handle, idx) orelse return error.JsException; - const js_val = js.Value{ .local = self.local, .handle = js_val_handle }; - return try self.local.valueToString(js_val, .{}); + const local = self.local; + const js_val_handle = v8.v8__Object__GetIndex(@ptrCast(self.handle), local.handle, idx) orelse return error.JsException; + return try js.Value.toStringSlice(.{ .local = local, .handle = js_val_handle }); } }; diff --git a/src/browser/js/String.zig b/src/browser/js/String.zig index 3d0ad6ad..04eda642 100644 --- a/src/browser/js/String.zig +++ b/src/browser/js/String.zig @@ -18,8 +18,10 @@ const std = @import("std"); const js = @import("js.zig"); +const SSO = @import("../../string.zig").String; const Allocator = std.mem.Allocator; +const IS_DEBUG = @import("builtin").mode == .Debug; const v8 = js.v8; @@ -28,26 +30,82 @@ const String = @This(); local: *const js.Local, handle: *const v8.String, -pub const ToZigOpts = struct { - allocator: ?Allocator = null, -}; - -pub fn toZig(self: String, opts: ToZigOpts) ![]u8 { - return self._toZig(false, opts); +pub fn toSlice(self: String) ![]u8 { + return self._toSlice(false, self.local.call_arena); } - -pub fn toZigZ(self: String, opts: ToZigOpts) ![:0]u8 { - return self._toZig(true, opts); +pub fn toSliceZ(self: String) ![:0]u8 { + return self._toSlice(true, self.local.call_arena); } +pub fn toSliceWithAlloc(self: String, allocator: Allocator) ![]u8 { + return self._toSlice(false, allocator); +} +fn _toSlice(self: String, comptime null_terminate: bool, allocator: Allocator) !(if (null_terminate) [:0]u8 else []u8) { + const local = self.local; + const handle = self.handle; + const isolate = local.isolate.handle; -fn _toZig(self: String, comptime null_terminate: bool, opts: ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) { - const isolate = self.local.isolate.handle; - const allocator = opts.allocator orelse self.local.ctx.call_arena; - const len: u32 = @intCast(v8.v8__String__Utf8Length(self.handle, isolate)); - const buf = if (null_terminate) try allocator.allocSentinel(u8, len, 0) else try allocator.alloc(u8, len); + const len = v8.v8__String__Utf8Length(handle, isolate); + const buf = try (if (comptime null_terminate) allocator.allocSentinel(u8, @intCast(len), 0) else allocator.alloc(u8, @intCast(len))); + const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); + if (comptime IS_DEBUG) { + std.debug.assert(n == len); + } - const options = v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8; - const n = v8.v8__String__WriteUtf8(self.handle, isolate, buf.ptr, buf.len, options); - std.debug.assert(n == len); return buf; } + +pub fn toSSO(self: String, comptime global: bool) !(if (global) SSO.Global else SSO) { + if (comptime global) { + return .{ .str = try self.toSSOWithAlloc(self.local.ctx.arena) }; + } + return self.toSSOWithAlloc(self.local.call_arena); +} +pub fn toSSOWithAlloc(self: String, allocator: Allocator) !SSO { + const handle = self.handle; + const isolate = self.local.isolate.handle; + + const len: usize = @intCast(v8.v8__String__Utf8Length(handle, isolate)); + + if (len <= 12) { + var content: [12]u8 = undefined; + const n = v8.v8__String__WriteUtf8(handle, isolate, &content[0], content.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); + if (comptime IS_DEBUG) { + std.debug.assert(n == len); + } + // Weird that we do this _after_, but we have to..I've seen weird issues + // in ReleaseMode where v8 won't write to content if it starts off zero + // initiated + @memset(content[len..], 0); + return .{ .len = @intCast(len), .payload = .{ .content = content } }; + } + + const buf = try allocator.alloc(u8, len); + const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); + if (comptime IS_DEBUG) { + std.debug.assert(n == len); + } + + var prefix: [4]u8 = @splat(0); + @memcpy(&prefix, buf[0..4]); + + return .{ + .len = @intCast(len), + .payload = .{ .heap = .{ + .prefix = prefix, + .ptr = buf.ptr, + } }, + }; +} + +pub fn format(self: String, writer: *std.Io.Writer) !void { + const local = self.local; + const handle = self.handle; + const isolate = local.isolate.handle; + + var small: [1024]u8 = undefined; + const len = v8.v8__String__Utf8Length(handle, isolate); + var buf = if (len < 1024) &small else local.call_arena.alloc(u8, @intCast(len)) catch return error.WriteFailed; + + const n = v8.v8__String__WriteUtf8(handle, isolate, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); + return writer.writeAll(buf[0..n]); +} diff --git a/src/browser/js/TryCatch.zig b/src/browser/js/TryCatch.zig index 8554e312..3b2e24f2 100644 --- a/src/browser/js/TryCatch.zig +++ b/src/browser/js/TryCatch.zig @@ -46,12 +46,12 @@ pub fn caught(self: TryCatch, allocator: Allocator) ?Caught { const exception: ?[]const u8 = blk: { const handle = v8.v8__TryCatch__Exception(&self.handle) orelse break :blk null; - break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err); + break :blk js.String.toSliceWithAlloc(.{ .local = l, .handle = @ptrCast(handle) }, allocator) catch |err| @errorName(err); }; const stack: ?[]const u8 = blk: { const handle = v8.v8__TryCatch__StackTrace(&self.handle, l.handle) orelse break :blk null; - break :blk l.valueHandleToString(@ptrCast(handle), .{ .allocator = allocator }) catch |err| @errorName(err); + break :blk js.String.toSliceWithAlloc(.{ .local = l, .handle = @ptrCast(handle) }, allocator) catch |err| @errorName(err); }; return .{ diff --git a/src/browser/js/Value.zig b/src/browser/js/Value.zig index d76313d5..7963ae7c 100644 --- a/src/browser/js/Value.zig +++ b/src/browser/js/Value.zig @@ -18,6 +18,7 @@ const std = @import("std"); const js = @import("js.zig"); +const SSO = @import("../../string.zig").String; const v8 = js.v8; @@ -34,8 +35,12 @@ pub fn isObject(self: Value) bool { return v8.v8__Value__IsObject(self.handle); } -pub fn isString(self: Value) bool { - return v8.v8__Value__IsString(self.handle); +pub fn isString(self: Value) ?js.String { + const handle = self.handle; + if (!v8.v8__Value__IsString(handle)) { + return null; + } + return .{ .local = self.local, .handle = @ptrCast(handle) }; } pub fn isArray(self: Value) bool { @@ -204,35 +209,40 @@ pub fn toPromise(self: Value) js.Promise { }; } -pub fn toString(self: Value, opts: js.String.ToZigOpts) ![]u8 { - return self._toString(false, opts); +pub fn toString(self: Value) !js.String { + const l = self.local; + const value_handle: *const v8.Value = blk: { + if (self.isSymbol()) { + break :blk @ptrCast(v8.v8__Symbol__Description(@ptrCast(self.handle), l.isolate.handle).?); + } + break :blk self.handle; + }; + + const str_handle = v8.v8__Value__ToString(value_handle, l.handle) orelse return error.JsException; + return .{ .local = self.local, .handle = str_handle }; } -pub fn toStringZ(self: Value, opts: js.String.ToZigOpts) ![:0]u8 { - return self._toString(true, opts); + +pub fn toSSO(self: Value, comptime global: bool) !(if (global) SSO.Global else SSO) { + return (try self.toString()).toSSO(global); +} +pub fn toSSOWithAlloc(self: Value, allocator: Allocator) !SSO { + return (try self.toString()).toSSOWithAlloc(allocator); +} + +pub fn toStringSlice(self: Value) ![]u8 { + return (try self.toString()).toSlice(); +} +pub fn toStringSliceZ(self: Value) ![:0]u8 { + return (try self.toString()).toSliceZ(); +} +pub fn toStringSliceWithAlloc(self: Value, allocator: Allocator) ![]u8 { + return (try self.toString()).toSliceWithAlloc(allocator); } pub fn toJson(self: Value, allocator: Allocator) ![]u8 { - const json_str_handle = v8.v8__JSON__Stringify(self.local.handle, self.handle, null) orelse return error.JsException; - return self.local.jsStringToZig(json_str_handle, .{ .allocator = allocator }); -} - -fn _toString(self: Value, comptime null_terminate: bool, opts: js.String.ToZigOpts) !(if (null_terminate) [:0]u8 else []u8) { - const l = self.local; - - if (self.isSymbol()) { - const sym_handle = v8.v8__Symbol__Description(@ptrCast(self.handle), l.isolate.handle).?; - return _toString(.{ .handle = @ptrCast(sym_handle), .local = l }, null_terminate, opts); - } - - const str_handle = v8.v8__Value__ToString(self.handle, l.handle) orelse { - return error.JsException; - }; - - const str = js.String{ .local = l, .handle = str_handle }; - if (comptime null_terminate) { - return js.String.toZigZ(str, opts); - } - return js.String.toZig(str, opts); + const local = self.local; + const str_handle = v8.v8__JSON__Stringify(local.handle, self.handle, null) orelse return error.JsException; + return js.String.toSliceWithAlloc(.{ .local = local, .handle = str_handle }, allocator); } pub fn persist(self: Value) !Global { @@ -296,8 +306,8 @@ pub fn format(self: Value, writer: *std.Io.Writer) !void { if (comptime IS_DEBUG) { return self.local.debugValue(self, writer); } - const str = self.toString(.{}) catch return error.WriteFailed; - return writer.writeAll(str); + const js_str = self.toString() catch return error.WriteFailed; + return js_str.format(writer); } pub const Temp = G(0); diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 9860d98f..a5ce1fae 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -422,7 +422,7 @@ pub fn unknownPropertyCallback(c_name: ?*const v8.Name, handle: ?*const v8.Prope hs.init(local.isolate); defer hs.deinit(); - const property: []const u8 = local.valueHandleToString(@ptrCast(c_name.?), .{}) catch { + const property: []const u8 = js.String.toSlice(.{ .local = local, .handle = @ptrCast(c_name.?) }) catch { return 0; }; diff --git a/src/browser/webapi/AbortSignal.zig b/src/browser/webapi/AbortSignal.zig index 8db60a70..0bb93886 100644 --- a/src/browser/webapi/AbortSignal.zig +++ b/src/browser/webapi/AbortSignal.zig @@ -116,7 +116,7 @@ pub fn throwIfAborted(self: *const AbortSignal, page: *Page) !ThrowIfAborted { if (self._aborted) { const exception = switch (self._reason) { .string => |str| local.throw(str), - .js_val => |js_val| local.throw(try local.toLocal(js_val).toString(.{ .allocator = page.call_arena })), + .js_val => |js_val| local.throw(try local.toLocal(js_val).toStringSlice()), .undefined => local.throw("AbortError"), }; return .{ .exception = exception }; diff --git a/src/browser/webapi/Console.zig b/src/browser/webapi/Console.zig index ab1fc12f..3318dc2c 100644 --- a/src/browser/webapi/Console.zig +++ b/src/browser/webapi/Console.zig @@ -146,7 +146,7 @@ const ValueWriter = struct { var buf: [32]u8 = undefined; for (self.values, 0..) |value, i| { const name = try std.fmt.bufPrint(&buf, "param.{d}", .{i}); - try writer.write(name, try value.toString(.{})); + try writer.write(name, value); } } diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index 461e7c6a..077d229b 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -73,9 +73,8 @@ pub fn define(self: *CustomElementRegistry, name: []const u8, constructor: js.Fu var js_arr = observed_attrs.toArray(); for (0..js_arr.len()) |i| { const attr_val = js_arr.get(@intCast(i)) catch continue; - const attr_name = attr_val.toString(.{ .allocator = page.arena }) catch continue; - const owned_attr = page.dupeString(attr_name) catch continue; - definition.observed_attributes.put(page.arena, owned_attr, {}) catch continue; + const attr_name = attr_val.toStringSliceWithAlloc(page.arena) catch continue; + definition.observed_attributes.put(page.arena, attr_name, {}) catch continue; } } } diff --git a/src/browser/webapi/KeyValueList.zig b/src/browser/webapi/KeyValueList.zig index 27ce6f7b..e4c55c49 100644 --- a/src/browser/webapi/KeyValueList.zig +++ b/src/browser/webapi/KeyValueList.zig @@ -68,12 +68,11 @@ pub fn fromJsObject(arena: Allocator, js_obj: js.Object, comptime normalizer: ?N while (try it.next()) |name| { const js_value = try js_obj.get(name); - const value = try js_value.toString(.{}); const normalized = if (comptime normalizer) |n| n(name, page) else name; list._entries.appendAssumeCapacity(.{ .name = try String.init(arena, normalized, .{}), - .value = try String.init(arena, value, .{}), + .value = try js_value.toSSOWithAlloc(arena), }); } diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 3e96cc89..8b275c86 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -277,7 +277,7 @@ pub fn cancelIdleCallback(self: *Window, id: u32) void { pub fn reportError(self: *Window, err: js.Value, page: *Page) !void { const error_event = try ErrorEvent.initTrusted("error", .{ .@"error" = try err.persist(), - .message = err.toString(.{}) catch "Unknown error", + .message = err.toStringSlice() catch "Unknown error", .bubbles = false, .cancelable = true, }, page); diff --git a/src/browser/webapi/net/URLSearchParams.zig b/src/browser/webapi/net/URLSearchParams.zig index e9384d28..cf13b81e 100644 --- a/src/browser/webapi/net/URLSearchParams.zig +++ b/src/browser/webapi/net/URLSearchParams.zig @@ -49,8 +49,8 @@ pub fn init(opts_: ?InitOpts, page: *Page) !*URLSearchParams { if (js_val.isObject()) { break :blk try KeyValueList.fromJsObject(arena, js_val.toObject(), null, page); } - if (js_val.isString()) { - break :blk try paramsFromString(arena, try js_val.toString(.{ .allocator = arena }), &page.buf); + if (js_val.isString()) |js_str| { + break :blk try paramsFromString(arena, try js_str.toSliceWithAlloc(arena), &page.buf); } return error.InvalidArgument; }, diff --git a/src/main_wpt.zig b/src/main_wpt.zig index a70aacc6..dbf2eea9 100644 --- a/src/main_wpt.zig +++ b/src/main_wpt.zig @@ -140,7 +140,7 @@ fn run( return err; }; - return value.toString(.{ .allocator = arena }); + return value.toStringSliceWithAlloc(arena); } const Writer = struct {