mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
Make js.Bridge aware of string.String for input parameters
Avoids having to allocate small strings when going from v8 -> Zig. Also added a discriminatory type, string.Global which uses the arena, rather than the call_arena, if an allocation _is_ necessary. (This is similar to a feature we had before, but was lost in zigdom). Strings from v8 that need to be persisted, can be allocated directly v8 -> arena, rather than v8 -> call_arena -> arena. I think there are a lot of places where we should use string.String - where strings are expected to be short (e.g. attribute names). But started with just document.querySelector and querySelectorAll.
This commit is contained in:
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const log = @import("../../log.zig");
|
const log = @import("../../log.zig");
|
||||||
|
const string = @import("../../string.zig");
|
||||||
|
|
||||||
const js = @import("js.zig");
|
const js = @import("js.zig");
|
||||||
const bridge = @import("bridge.zig");
|
const bridge = @import("bridge.zig");
|
||||||
@@ -619,6 +620,19 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T {
|
|||||||
};
|
};
|
||||||
return try promise.persist();
|
return try promise.persist();
|
||||||
},
|
},
|
||||||
|
string.String => {
|
||||||
|
if (!js_val.isString()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return try self.valueToStringSSO(js_val, .{.allocator = self.ctx.call_arena});
|
||||||
|
},
|
||||||
|
string.Global => {
|
||||||
|
if (!js_val.isString()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Use arena for persistent strings
|
||||||
|
return .{.str = try self.valueToStringSSO(js_val, .{ .allocator = self.ctx.arena }) };
|
||||||
|
},
|
||||||
else => {
|
else => {
|
||||||
if (!js_val.isObject()) {
|
if (!js_val.isObject()) {
|
||||||
return null;
|
return null;
|
||||||
@@ -927,6 +941,15 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
.@"struct" => {
|
.@"struct" => {
|
||||||
|
// Handle string.String and string.Global specially
|
||||||
|
if (T == string.String or T == string.Global) {
|
||||||
|
if (js_val.isString()) {
|
||||||
|
return .{ .ok = {} };
|
||||||
|
}
|
||||||
|
// Anything can be coerced to a string
|
||||||
|
return .{ .coerce = {} };
|
||||||
|
}
|
||||||
|
|
||||||
// We don't want to duplicate the code for this, so we call
|
// We don't want to duplicate the code for this, so we call
|
||||||
// the actual conversion function.
|
// the actual conversion function.
|
||||||
const value = (try self.jsValueToStruct(T, js_val)) orelse {
|
const value = (try self.jsValueToStruct(T, js_val)) orelse {
|
||||||
@@ -1118,6 +1141,46 @@ fn _jsStringToZig(self: *const Local, comptime null_terminate: bool, str: anytyp
|
|||||||
return buf;
|
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 = @splat(0);
|
||||||
|
const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, &content, len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
|
||||||
|
if (comptime IS_DEBUG) {
|
||||||
|
std.debug.assert(n == len);
|
||||||
|
}
|
||||||
|
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 ==
|
// == Promise Helpers ==
|
||||||
pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise {
|
pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise {
|
||||||
var resolver = js.PromiseResolver.init(self);
|
var resolver = js.PromiseResolver.init(self);
|
||||||
|
|||||||
@@ -282,12 +282,12 @@ pub fn getSelection(self: *Document) *Selection {
|
|||||||
return &self._selection;
|
return &self._selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelector(self: *Document, input: []const u8, page: *Page) !?*Element {
|
pub fn querySelector(self: *Document, input: String, page: *Page) !?*Element {
|
||||||
return Selector.querySelector(self.asNode(), input, page);
|
return Selector.querySelector(self.asNode(), input.str(), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn querySelectorAll(self: *Document, input: []const u8, page: *Page) !*Selector.List {
|
pub fn querySelectorAll(self: *Document, input: String, page: *Page) !*Selector.List {
|
||||||
return Selector.querySelectorAll(self.asNode(), input, page);
|
return Selector.querySelectorAll(self.asNode(), input.str(), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getImplementation(_: *const Document) DOMImplementation {
|
pub fn getImplementation(_: *const Document) DOMImplementation {
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ test "cdp Node: Registry register" {
|
|||||||
var doc = page.window._document;
|
var doc = page.window._document;
|
||||||
|
|
||||||
{
|
{
|
||||||
const dom_node = (try doc.querySelector("#a1", page)).?.asNode();
|
const dom_node = (try doc.querySelector(testing.newString("#a1"), page)).?.asNode();
|
||||||
const node = try registry.register(dom_node);
|
const node = try registry.register(dom_node);
|
||||||
const n1b = registry.lookup_by_id.get(1).?;
|
const n1b = registry.lookup_by_id.get(1).?;
|
||||||
const n1c = registry.lookup_by_node.get(node.dom).?;
|
const n1c = registry.lookup_by_node.get(node.dom).?;
|
||||||
@@ -356,7 +356,7 @@ test "cdp Node: Registry register" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const dom_node = (try doc.querySelector("p", page)).?.asNode();
|
const dom_node = (try doc.querySelector(testing.newString("p"), page)).?.asNode();
|
||||||
const node = try registry.register(dom_node);
|
const node = try registry.register(dom_node);
|
||||||
const n1b = registry.lookup_by_id.get(2).?;
|
const n1b = registry.lookup_by_id.get(2).?;
|
||||||
const n1c = registry.lookup_by_node.get(node.dom).?;
|
const n1c = registry.lookup_by_node.get(node.dom).?;
|
||||||
@@ -400,18 +400,18 @@ test "cdp Node: search list" {
|
|||||||
defer page._session.removePage();
|
defer page._session.removePage();
|
||||||
var doc = page.window._document;
|
var doc = page.window._document;
|
||||||
|
|
||||||
const s1 = try search_list.create((try doc.querySelectorAll("a", page))._nodes);
|
const s1 = try search_list.create((try doc.querySelectorAll(testing.newString("a"), page))._nodes);
|
||||||
try testing.expectEqual("1", s1.name);
|
try testing.expectEqual("1", s1.name);
|
||||||
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
|
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
|
||||||
|
|
||||||
try testing.expectEqual(2, registry.lookup_by_id.count());
|
try testing.expectEqual(2, registry.lookup_by_id.count());
|
||||||
try testing.expectEqual(2, registry.lookup_by_node.count());
|
try testing.expectEqual(2, registry.lookup_by_node.count());
|
||||||
|
|
||||||
const s2 = try search_list.create((try doc.querySelectorAll("#a1", page))._nodes);
|
const s2 = try search_list.create((try doc.querySelectorAll(testing.newString("#a1"), page))._nodes);
|
||||||
try testing.expectEqual("2", s2.name);
|
try testing.expectEqual("2", s2.name);
|
||||||
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
|
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
|
||||||
|
|
||||||
const s3 = try search_list.create((try doc.querySelectorAll("#a2", page))._nodes);
|
const s3 = try search_list.create((try doc.querySelectorAll(testing.newString("#a2"), page))._nodes);
|
||||||
try testing.expectEqual("3", s3.name);
|
try testing.expectEqual("3", s3.name);
|
||||||
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);
|
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub const expectEqual = base.expectEqual;
|
|||||||
pub const expectError = base.expectError;
|
pub const expectError = base.expectError;
|
||||||
pub const expectEqualSlices = base.expectEqualSlices;
|
pub const expectEqualSlices = base.expectEqualSlices;
|
||||||
pub const pageTest = base.pageTest;
|
pub const pageTest = base.pageTest;
|
||||||
|
pub const newString = base.newString;
|
||||||
|
|
||||||
const Client = struct {
|
const Client = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
|
|||||||
@@ -271,17 +271,21 @@ pub const Header = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Headers = struct {
|
pub const Headers = struct {
|
||||||
headers: *c.curl_slist,
|
headers: ?*c.curl_slist,
|
||||||
cookies: ?[*c]const u8,
|
cookies: ?[*c]const u8,
|
||||||
|
|
||||||
pub fn init(user_agent: [:0]const u8) !Headers {
|
pub fn init(user_agent: [:0]const u8) !Headers {
|
||||||
const header_list = c.curl_slist_append(null, user_agent);
|
const header_list = c.curl_slist_append(null, user_agent);
|
||||||
if (header_list == null) return error.OutOfMemory;
|
if (header_list == null) {
|
||||||
|
return error.OutOfMemory;
|
||||||
|
}
|
||||||
return .{ .headers = header_list, .cookies = null };
|
return .{ .headers = header_list, .cookies = null };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *const Headers) void {
|
pub fn deinit(self: *const Headers) void {
|
||||||
c.curl_slist_free_all(self.headers);
|
if (self.headers) |hdr| {
|
||||||
|
c.curl_slist_free_all(hdr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(self: *Headers, header: [*c]const u8) !void {
|
pub fn add(self: *Headers, header: [*c]const u8) !void {
|
||||||
|
|||||||
@@ -188,6 +188,13 @@ pub const String = packed struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Discriminatory type that signals the bridge to use arena instead of call_arena
|
||||||
|
// Use this for strings that need to persist beyond the current call
|
||||||
|
// The caller can unwrap and store just the underlying .str field
|
||||||
|
pub const Global = struct {
|
||||||
|
str: String,
|
||||||
|
};
|
||||||
|
|
||||||
fn asUint(comptime string: anytype) std.meta.Int(
|
fn asUint(comptime string: anytype) std.meta.Int(
|
||||||
.unsigned,
|
.unsigned,
|
||||||
@bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0
|
@bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0
|
||||||
|
|||||||
@@ -176,6 +176,11 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const String = @import("string.zig").String;
|
||||||
|
pub fn newString(str: []const u8) String {
|
||||||
|
return String.init(arena_allocator, str, .{}) catch unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Random = struct {
|
pub const Random = struct {
|
||||||
var instance: ?std.Random.DefaultPrng = null;
|
var instance: ?std.Random.DefaultPrng = null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user