webapi: Give iterator to types with an indexed property

Apparently, any type that ah indexed property also gets an iterator, even if
the iterator isn't explicitly listed in the spec. This showed up as a bug
rendering washingtonpost.com.
This commit is contained in:
Karl Seguin committed 2026-08-14 16:43:33 +08:00
1 parent 18afa56eb6
commit b53fa18c3d
5 files changed
+104 -1

No files matched your search

+36
View File
@@ -24,6 +24,42 @@
}
</script>
<script id="StyleSheetList_iterable">
{
// WebIDL: indexed getter => @@iterator, but no iterable<> declaration,
// so no keys/values/entries. Stitches does [...document.styleSheets].
const sheets = document.styleSheets;
testing.expectEqual('function', typeof sheets[Symbol.iterator]);
testing.expectEqual(undefined, sheets.values);
testing.expectEqual(0, [...sheets].length);
const style = document.createElement('style');
document.head.appendChild(style);
style.sheet.insertRule('.it-a { color: green; }', 0);
style.sheet.insertRule('.it-b { color: blue; }', 1);
const collected = [...document.styleSheets];
testing.expectEqual(1, collected.length);
testing.expectTrue(collected[0] === sheets[0]);
const rules = style.sheet.cssRules;
testing.expectEqual('function', typeof rules[Symbol.iterator]);
testing.expectEqual(undefined, rules.values);
const selectors = [...rules].map(r => r.selectorText);
testing.expectEqual('.it-a,.it-b', selectors.join(','));
// The iterator is live: rules removed mid-iteration are not visited
const seen = [];
for (const rule of rules) {
seen.push(rule.selectorText);
style.sheet.deleteRule(1);
}
testing.expectEqual('.it-a', seen.join(','));
document.head.removeChild(style);
}
</script>
<script id="CSSStyleDeclaration_setCssText_basic">
{
const div = document.createElement('div');
@@ -51,6 +51,12 @@
}
</script>
<script id=navigator_plugins_iterable>
// WebIDL: indexed getter => @@iterator (Chrome: [...navigator.plugins] works)
testing.expectEqual('function', typeof navigator.plugins[Symbol.iterator]);
testing.expectEqual(0, [...navigator.plugins].length);
</script>
<script id=navigator_native_descriptor_walk>
// Mirror the bot-detection fingerprint: read every navigator property's
// descriptor and call toString() on its value/getter. This must not throw.
+16 -1
View File
@@ -17,9 +17,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../js/js.zig");
const Frame = @import("../Frame.zig");
const GenericIterator = @import("collections/iterator.zig").Entry;
pub fn registerTypes() []const type {
return &.{ PluginArray, Plugin };
return &.{ PluginArray, Plugin, ValueIterator };
}
const PluginArray = @This();
@@ -51,6 +53,18 @@ const Plugin = struct {
};
};
fn values(_: *const PluginArray, frame: *Frame) !*ValueIterator {
return .init(.{}, frame);
}
const ValueIterator = GenericIterator(Iterator, null);
const Iterator = struct {
pub fn next(_: *Iterator, _: *Frame) ?*Plugin {
return null;
}
};
pub const JsApi = struct {
pub const bridge = js.Bridge(PluginArray);
@@ -73,4 +87,5 @@ pub const JsApi = struct {
return self.getAtIndex(@intCast(index));
}
pub const namedItem = bridge.function(PluginArray.getByName, .{});
pub const symbol_iterator = bridge.iterator(PluginArray.values, .{});
};
+23
View File
@@ -2,9 +2,14 @@ const std = @import("std");
const js = @import("../../js/js.zig");
const Frame = @import("../../Frame.zig");
const CSSRule = @import("CSSRule.zig");
const GenericIterator = @import("../collections/iterator.zig").Entry;
const CSSRuleList = @This();
pub fn registerTypes() []const type {
return &.{ CSSRuleList, ValueIterator };
}
_rules: std.ArrayList(*CSSRule) = .empty,
pub fn init(frame: *Frame) !*CSSRuleList {
@@ -40,6 +45,23 @@ pub fn clear(self: *CSSRuleList) void {
self._rules.clearRetainingCapacity();
}
fn values(self: *CSSRuleList, frame: *Frame) !*ValueIterator {
return .init(.{ .list = self }, frame);
}
const ValueIterator = GenericIterator(Iterator, null);
const Iterator = struct {
index: u32 = 0,
list: *CSSRuleList,
pub fn next(self: *Iterator, _: *Frame) ?*CSSRule {
const rule = self.list.item(self.index) orelse return null;
self.index += 1;
return rule;
}
};
pub const JsApi = struct {
pub const bridge = js.Bridge(CSSRuleList);
@@ -51,4 +73,5 @@ pub const JsApi = struct {
pub const length = bridge.accessor(CSSRuleList.length, null, .{});
pub const @"[]" = bridge.indexed(CSSRuleList.item, null, .{ .null_as_undefined = true });
pub const symbol_iterator = bridge.iterator(CSSRuleList.values, .{});
};
+23
View File
@@ -2,9 +2,14 @@ const std = @import("std");
const js = @import("../../js/js.zig");
const Frame = @import("../../Frame.zig");
const CSSStyleSheet = @import("CSSStyleSheet.zig");
const GenericIterator = @import("../collections/iterator.zig").Entry;
const StyleSheetList = @This();
pub fn registerTypes() []const type {
return &.{ StyleSheetList, ValueIterator };
}
_sheets: std.ArrayList(*CSSStyleSheet) = .empty,
pub fn init(frame: *Frame) !*StyleSheetList {
@@ -33,6 +38,23 @@ pub fn remove(self: *StyleSheetList, sheet: *CSSStyleSheet) void {
}
}
fn values(self: *StyleSheetList, frame: *Frame) !*ValueIterator {
return .init(.{ .list = self }, frame);
}
const ValueIterator = GenericIterator(Iterator, null);
const Iterator = struct {
index: u32 = 0,
list: *StyleSheetList,
pub fn next(self: *Iterator, _: *Frame) ?*CSSStyleSheet {
const sheet = self.list.item(self.index) orelse return null;
self.index += 1;
return sheet;
}
};
pub const JsApi = struct {
pub const bridge = js.Bridge(StyleSheetList);
@@ -44,4 +66,5 @@ pub const JsApi = struct {
pub const length = bridge.accessor(StyleSheetList.length, null, .{});
pub const @"[]" = bridge.indexed(StyleSheetList.item, null, .{ .null_as_undefined = true });
pub const symbol_iterator = bridge.iterator(StyleSheetList.values, .{});
};