Merge pull request #2886 from lightpanda-io/loader_feature_detect

webapi: implement HTMLScriptElement.supports and DOMTokenList.supports
This commit is contained in:
Karl Seguin
2026-07-05 08:02:59 +08:00
committed by GitHub
4 changed files with 64 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<script src="../../../testing.js"></script>
<script id=supports>
{
testing.expectEqual(true, HTMLScriptElement.supports('classic'));
testing.expectEqual(true, HTMLScriptElement.supports('module'));
testing.expectEqual(true, HTMLScriptElement.supports('importmap'));
testing.expectEqual(false, HTMLScriptElement.supports('speculationrules'));
testing.expectEqual(false, HTMLScriptElement.supports('Module'));
}
</script>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<link id=l1 rel="stylesheet" href="data:text/css,">
<div id=d1 class="foo"></div>
<script id=supports>
{
const rl = $('#l1').relList;
testing.expectEqual(true, rl.supports('modulepreload'));
testing.expectEqual(true, rl.supports('MODULEPRELOAD'));
testing.expectEqual(true, rl.supports('preload'));
testing.expectEqual(true, rl.supports('stylesheet'));
testing.expectEqual(false, rl.supports('prefetch'));
}
</script>
<script id=supports_undefined_tokens>
{
// classList's backing attribute defines no supported tokens.
testing.expectError('TypeError', () => $('#d1').classList.supports('foo'));
}
</script>

View File

@@ -69,6 +69,22 @@ pub fn item(self: *const DOMTokenList, index: usize, frame: *Frame) !?[]const u8
return null;
}
/// https://dom.spec.whatwg.org/#dom-domtokenlist-supports
/// Only `rel` defines supported tokens here; per spec every other backing
/// attribute throws. Loaders probe `relList.supports("modulepreload")` and
/// fall back to fetch()-based legacy loading when it fails.
pub fn supports(self: *const DOMTokenList, token: []const u8, frame: *Frame) !bool {
if (!std.ascii.eqlIgnoreCase(self._attribute_name.str(), "rel")) {
return error.TypeError;
}
const supported = [_][]const u8{ "stylesheet", "preload", "modulepreload" };
const lower = try std.ascii.allocLowerString(frame.local_arena, token);
for (supported) |s| {
if (std.mem.eql(u8, lower, s)) return true;
}
return false;
}
pub fn contains(self: *const DOMTokenList, search: []const u8) !bool {
var it = std.mem.tokenizeAny(u8, self.getValue(), WHITESPACE);
while (it.next()) |token| {
@@ -310,6 +326,7 @@ pub const JsApi = struct {
}
pub const contains = bridge.function(DOMTokenList.contains, .{ .dom_exception = true });
pub const supports = bridge.function(DOMTokenList.supports, .{ .dom_exception = true });
pub const add = bridge.function(DOMTokenList.add, .{ .dom_exception = true, .ce_reactions = true });
pub const remove = bridge.function(DOMTokenList.remove, .{ .dom_exception = true, .ce_reactions = true });
pub const toggle = bridge.function(DOMTokenList.toggle, .{ .dom_exception = true, .ce_reactions = true });

View File

@@ -115,6 +115,17 @@ pub fn setInnerText(self: *Script, text: []const u8, frame: *Frame) !void {
try self.asNode().setTextContent(text, frame);
}
/// https://html.spec.whatwg.org/multipage/scripting.html#dom-script-supports
/// Only the types this engine actually loads; loaders feature-detect module
/// support here and fall back to fetch()-based legacy loading when it fails.
pub fn supports(value: []const u8) bool {
const supported = [_][]const u8{ "classic", "module", "importmap" };
for (supported) |s| {
if (std.mem.eql(u8, value, s)) return true;
}
return false;
}
pub const JsApi = struct {
pub const bridge = js.Bridge(Script);
@@ -131,6 +142,7 @@ pub const JsApi = struct {
pub const nonce = bridge.accessor(Script.getNonce, Script.setNonce, .{ .ce_reactions = true });
pub const charset = bridge.accessor(Script.getCharset, Script.setCharset, .{ .ce_reactions = true });
pub const noModule = bridge.accessor(Script.getNoModule, null, .{});
pub const supports = bridge.function(Script.supports, .{ .static = true });
pub const innerText = bridge.accessor(_innerText, Script.setInnerText, .{ .ce_reactions = true });
fn _innerText(self: *Script, frame: *const Frame) ![]const u8 {
var buf = std.Io.Writer.Allocating.init(frame.call_arena);