Merge pull request #1610 from lightpanda-io/add_js_nullablestring

Add js.NullableString
This commit is contained in:
Karl Seguin
2026-02-20 15:30:15 +08:00
committed by GitHub
8 changed files with 49 additions and 13 deletions

View File

@@ -453,6 +453,13 @@ pub fn jsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !T {
return js_val;
}
if (comptime o.child == js.NullableString) {
if (js_val.isUndefined()) {
return null;
}
return .{ .value = try js_val.toStringSlice() };
}
if (comptime o.child == js.Object) {
return js.Object{
.local = self,

View File

@@ -168,6 +168,16 @@ pub fn ArrayBufferRef(comptime kind: ArrayType) type {
};
}
// If a WebAPI takes a []const u8, then we'll coerce any JS value to that string
// so null -> "null". But if a WebAPI takes an optional string, ?[]const u8,
// how should we handle null? If the parameter _isn't_ passed, then it's obvious
// that it should be null, but what if `null` is passed? It's ambiguous, should
// that be null, or "null"? It could depend on the api. So, `null` passed to
// ?[]const u8 will be `null`. If you want it to be "null", use a `.js.NullableString`.
pub const NullableString = struct {
value: []const u8,
};
pub const Exception = struct {
local: *const Local,
handle: *const v8.Value,

View File

@@ -4,4 +4,6 @@
<script id=comment>
testing.expectEqual('', new Comment().data);
testing.expectEqual('over 9000! ', new Comment('over 9000! ').data);
testing.expectEqual('null', new Comment(null).data);
</script>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<a id="link" href="foo" class="ok">OK</a>
<script src="../../testing.js"></script>
<script src="../testing.js"></script>
<script id=text>
let t = new Text('foo');
testing.expectEqual('foo', t.data);
@@ -16,4 +16,7 @@
let split = text.splitText('OK'.length);
testing.expectEqual(' modified', split.data);
testing.expectEqual('OK', text.data);
let x = new Text(null);
testing.expectEqual("null", x.data);
</script>

View File

@@ -108,6 +108,20 @@
}
</script>
<script id=createHTMLDocument_nulll_title>
{
const impl = document.implementation;
const doc = impl.createHTMLDocument(null);
testing.expectEqual('null', doc.title);
// Should have title element in head
const titleElement = doc.head.querySelector('title');
testing.expectEqual(true, titleElement !== null);
testing.expectEqual('null', titleElement.textContent);
}
</script>
<script id=createHTMLDocument_structure>
{
const impl = document.implementation;

View File

@@ -31,7 +31,7 @@ pub fn createDocumentType(_: *const DOMImplementation, qualified_name: []const u
return DocumentType.init(qualified_name, public_id, system_id, page);
}
pub fn createHTMLDocument(_: *const DOMImplementation, title: ?[]const u8, page: *Page) !*Document {
pub fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, page: *Page) !*Document {
const document = (try page._factory.document(Node.Document.HTMLDocument{ ._proto = undefined })).asDocument();
document._ready_state = .complete;
document._url = "about:blank";
@@ -55,7 +55,7 @@ pub fn createHTMLDocument(_: *const DOMImplementation, title: ?[]const u8, page:
if (title) |t| {
const title_node = try page.createElementNS(.html, "title", null);
_ = try head_node.appendChild(title_node, page);
const text_node = try page.createTextNode(t);
const text_node = try page.createTextNode(t.value);
_ = try title_node.appendChild(text_node, page);
}

View File

@@ -25,8 +25,8 @@ const Comment = @This();
_proto: *CData,
pub fn init(content: ?[]const u8, page: *Page) !*Comment {
const node = try page.createComment(content orelse "");
pub fn init(str: ?js.NullableString, page: *Page) !*Comment {
const node = try page.createComment(if (str) |s| s.value else "");
return node.as(Comment);
}
@@ -42,3 +42,8 @@ pub const JsApi = struct {
pub const constructor = bridge.constructor(Comment.init, .{});
};
const testing = @import("../../../testing.zig");
test "WebApi: CData.Text" {
try testing.htmlRunner("cdata/comment.html", .{});
}

View File

@@ -16,6 +16,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../../js/js.zig");
const Page = @import("../../Page.zig");
const CData = @import("../CData.zig");
@@ -23,8 +24,8 @@ const Text = @This();
_proto: *CData,
pub fn init(str: ?[]const u8, page: *Page) !*Text {
const node = try page.createTextNode(str orelse "");
pub fn init(str: ?js.NullableString, page: *Page) !*Text {
const node = try page.createTextNode(if (str) |s| s.value else "");
return node.as(Text);
}
@@ -54,13 +55,7 @@ pub fn splitText(self: *Text, offset: usize, page: *Page) !*Text {
return new_text;
}
const testing = @import("../../../testing.zig");
test "WebApi: CData.Text" {
try testing.htmlRunner("cdata/text", .{});
}
pub const JsApi = struct {
const js = @import("../../js/js.zig");
pub const bridge = js.Bridge(Text);
pub const Meta = struct {