Merge pull request #2721 from rohitsux/feat/html-source-reflection

feat(webapi): reflect attributes on HTMLSourceElement
This commit is contained in:
Karl Seguin
2026-06-12 18:54:18 +08:00
committed by GitHub
2 changed files with 162 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<script src="../../testing.js"></script>
<source id="s1" src="/video.webm" srcset="/img-1x.png 1x, /img-2x.png 2x" sizes="100vw" media="(min-width: 600px)" type="video/webm" width="640" height="480">
<source id="s2">
<script id="source-from-html">
{
const s1 = document.getElementById('s1');
testing.expectEqual('HTMLSourceElement', s1.constructor.name);
testing.expectEqual(testing.ORIGIN + '/video.webm', s1.src);
testing.expectEqual('/img-1x.png 1x, /img-2x.png 2x', s1.srcset);
testing.expectEqual('100vw', s1.sizes);
testing.expectEqual('(min-width: 600px)', s1.media);
testing.expectEqual('video/webm', s1.type);
testing.expectEqual(640, s1.width);
testing.expectEqual(480, s1.height);
const s2 = document.getElementById('s2');
testing.expectEqual('', s2.src);
testing.expectEqual('', s2.srcset);
testing.expectEqual('', s2.sizes);
testing.expectEqual('', s2.media);
testing.expectEqual('', s2.type);
testing.expectEqual(0, s2.width);
testing.expectEqual(0, s2.height);
}
</script>
<script id="source-reflection">
{
const s = document.createElement('source');
s.srcset = '/a.png 1x';
testing.expectEqual('/a.png 1x', s.getAttribute('srcset'));
s.setAttribute('srcset', '/b.png 2x');
testing.expectEqual('/b.png 2x', s.srcset);
s.sizes = '50vw';
testing.expectEqual('50vw', s.getAttribute('sizes'));
s.setAttribute('sizes', '25vw');
testing.expectEqual('25vw', s.sizes);
s.media = 'screen';
testing.expectEqual('screen', s.getAttribute('media'));
s.setAttribute('media', 'print');
testing.expectEqual('print', s.media);
s.type = 'video/mp4';
testing.expectEqual('video/mp4', s.getAttribute('type'));
s.setAttribute('type', 'image/svg+xml');
testing.expectEqual('image/svg+xml', s.type);
s.width = 320;
testing.expectEqual('320', s.getAttribute('width'));
s.setAttribute('width', '160');
testing.expectEqual(160, s.width);
s.setAttribute('width', 'not-a-number');
testing.expectEqual(0, s.width);
s.height = 240;
testing.expectEqual('240', s.getAttribute('height'));
s.setAttribute('height', '120');
testing.expectEqual(120, s.height);
}
</script>
<script id="source-src-url">
{
const s = document.createElement('source');
testing.expectEqual('', s.src);
s.src = 'https://lightpanda.io/clip.webm';
testing.expectEqual('https://lightpanda.io/clip.webm', s.src);
s.src = '/relative.webm';
testing.expectEqual(testing.ORIGIN + '/relative.webm', s.src);
}
</script>

View File

@@ -1,4 +1,6 @@
const std = @import("std");
const js = @import("../../../js/js.zig");
const Frame = @import("../../../Frame.zig");
const Node = @import("../../Node.zig");
const Element = @import("../../Element.zig");
const HtmlElement = @import("../Html.zig");
@@ -10,10 +12,78 @@ _proto: *HtmlElement,
pub fn asElement(self: *Source) *Element {
return self._proto._proto;
}
pub fn asConstElement(self: *const Source) *const Element {
return self._proto._proto;
}
pub fn asNode(self: *Source) *Node {
return self.asElement().asNode();
}
pub fn getSrc(self: *const Source, frame: *Frame) ![]const u8 {
const element = self.asConstElement();
const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
if (src.len == 0) {
return "";
}
return element.asConstNode().resolveURL(src, frame, .{});
}
pub fn setSrc(self: *Source, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), frame);
}
pub fn getSrcset(self: *const Source) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("srcset")) orelse "";
}
pub fn setSrcset(self: *Source, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("srcset"), .wrap(value), frame);
}
pub fn getSizes(self: *const Source) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("sizes")) orelse "";
}
pub fn setSizes(self: *Source, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("sizes"), .wrap(value), frame);
}
pub fn getMedia(self: *const Source) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("media")) orelse "";
}
pub fn setMedia(self: *Source, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("media"), .wrap(value), frame);
}
pub fn getType(self: *const Source) []const u8 {
return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "";
}
pub fn setType(self: *Source, value: []const u8, frame: *Frame) !void {
try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), frame);
}
pub fn getWidth(self: *const Source) u32 {
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
}
pub fn setWidth(self: *Source, value: u32, frame: *Frame) !void {
const str = try std.fmt.allocPrint(frame.call_arena, "{d}", .{value});
try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), frame);
}
pub fn getHeight(self: *const Source) u32 {
const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
}
pub fn setHeight(self: *Source, value: u32, frame: *Frame) !void {
const str = try std.fmt.allocPrint(frame.call_arena, "{d}", .{value});
try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), frame);
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Source);
@@ -22,4 +92,17 @@ pub const JsApi = struct {
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
pub const height = bridge.accessor(Source.getHeight, Source.setHeight, .{ .ce_reactions = true });
pub const media = bridge.accessor(Source.getMedia, Source.setMedia, .{ .ce_reactions = true });
pub const sizes = bridge.accessor(Source.getSizes, Source.setSizes, .{ .ce_reactions = true });
pub const src = bridge.accessor(Source.getSrc, Source.setSrc, .{ .ce_reactions = true });
pub const srcset = bridge.accessor(Source.getSrcset, Source.setSrcset, .{ .ce_reactions = true });
pub const @"type" = bridge.accessor(Source.getType, Source.setType, .{ .ce_reactions = true });
pub const width = bridge.accessor(Source.getWidth, Source.setWidth, .{ .ce_reactions = true });
};
const testing = @import("../../../../testing.zig");
test "WebApi: HTML.Source" {
try testing.htmlRunner("element/html/source.html", .{});
}