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.
This commit is contained in:
Karl Seguin
2026-01-29 14:21:24 +08:00
parent 232e7a1759
commit c84106570f
18 changed files with 173 additions and 205 deletions

View File

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

View File

@@ -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 {

View File

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

View File

@@ -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";

View File

@@ -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}<anonymous>:{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) {

View File

@@ -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).? };
}
};

View File

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

View File

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

View File

@@ -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 .{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
},

View File

@@ -140,7 +140,7 @@ fn run(
return err;
};
return value.toString(.{ .allocator = arena });
return value.toStringSliceWithAlloc(arena);
}
const Writer = struct {