From 3dcbb46834dccfacf95b5c71220e0746a4cdcbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Sat, 4 Jul 2026 20:27:26 +0200 Subject: [PATCH 1/2] webapi: implement HTMLScriptElement.supports and DOMTokenList.supports Module loaders (Vite legacy plugin, Shopify themes) feature-detect HTMLScriptElement.supports("module") and link.relList.supports("modulepreload"). Both were missing, so themes fell back to fetch()-based legacy chunk loading: on allbirds.com that meant 676 requests instead of 305 for two page loads, the same module fetched 9x per page by page JS, and a ~4% CDP driver flake from the legacy path replacing the document mid-query (0/50 repro with this change, vs failing within 3 iterations without). HTMLScriptElement.supports returns true for the types this engine loads (classic, module, importmap). DOMTokenList.supports implements the rel supported-tokens set and throws TypeError for attributes that define none, per spec. --- .../tests/element/html/script/supports.html | 12 ++++++++++ src/browser/tests/element/rel_list.html | 23 +++++++++++++++++++ .../webapi/collections/DOMTokenList.zig | 17 ++++++++++++++ src/browser/webapi/element/html/Script.zig | 12 ++++++++++ 4 files changed, 64 insertions(+) create mode 100644 src/browser/tests/element/html/script/supports.html create mode 100644 src/browser/tests/element/rel_list.html diff --git a/src/browser/tests/element/html/script/supports.html b/src/browser/tests/element/html/script/supports.html new file mode 100644 index 000000000..4011b71b0 --- /dev/null +++ b/src/browser/tests/element/html/script/supports.html @@ -0,0 +1,12 @@ + + + + diff --git a/src/browser/tests/element/rel_list.html b/src/browser/tests/element/rel_list.html new file mode 100644 index 000000000..28b231753 --- /dev/null +++ b/src/browser/tests/element/rel_list.html @@ -0,0 +1,23 @@ + + + + +
+ + + + diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index ee7a5f964..0a7e8a752 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -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.call_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 }); diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index 064b706bd..8b569ceb5 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -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); From 83c412765c423343ee056950894a216642fa0588 Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sun, 5 Jul 2026 07:49:07 +0800 Subject: [PATCH 2/2] call_arena -> local_arena --- src/browser/webapi/collections/DOMTokenList.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index 0a7e8a752..2c40b7a35 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -78,7 +78,7 @@ pub fn supports(self: *const DOMTokenList, token: []const u8, frame: *Frame) !bo return error.TypeError; } const supported = [_][]const u8{ "stylesheet", "preload", "modulepreload" }; - const lower = try std.ascii.allocLowerString(frame.call_arena, token); + const lower = try std.ascii.allocLowerString(frame.local_arena, token); for (supported) |s| { if (std.mem.eql(u8, lower, s)) return true; }