From ff0fbb6b41d985e03ceaf70a92750e015b2be5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Mon, 23 Mar 2026 23:45:11 +0900 Subject: [PATCH 1/4] Fix Expo Web crash by gracefully handling at-rules in CSSStyleSheet.insertRule --- src/browser/webapi/css/CSSStyleSheet.zig | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index 675e87f9..52dcbf21 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -75,7 +75,15 @@ pub fn getOwnerRule(self: *const CSSStyleSheet) ?*CSSRule { pub fn insertRule(self: *CSSStyleSheet, rule: []const u8, maybe_index: ?u32, page: *Page) !u32 { const index = maybe_index orelse 0; var it = Parser.parseStylesheet(rule); - const parsed_rule = it.next() orelse return error.SyntaxError; + const parsed_rule = it.next() orelse { + const trimmed = std.mem.trimLeft(u8, rule, &std.ascii.whitespace); + if (std.mem.startsWith(u8, trimmed, "@")) { + // At-rules (like @keyframes) are currently skipped by the parser. + // Returning the index simulates successful insertion without crashing. + return index; + } + return error.SyntaxError; + }; const style_rule = try CSSStyleRule.init(page); try style_rule.setSelectorText(parsed_rule.selector, page); From 5972630e951e707ece119d98f012f90df4086b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Tue, 24 Mar 2026 00:54:20 +0900 Subject: [PATCH 2/4] Update CSS parser to track skipped at-rules and refine insertRule logic --- src/browser/css/Parser.zig | 2 ++ src/browser/webapi/css/CSSStyleSheet.zig | 9 +++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/browser/css/Parser.zig b/src/browser/css/Parser.zig index 401baecb..53488d96 100644 --- a/src/browser/css/Parser.zig +++ b/src/browser/css/Parser.zig @@ -306,6 +306,7 @@ pub fn parseStylesheet(input: []const u8) RulesIterator { pub const RulesIterator = struct { input: []const u8, stream: TokenStream, + has_skipped_at_rule: bool = false, pub fn init(input: []const u8) RulesIterator { return .{ @@ -358,6 +359,7 @@ pub const RulesIterator = struct { } if (peeked.token == .at_keyword) { + self.has_skipped_at_rule = true; self.skipAtRule(); selector_start = null; selector_end = null; diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index 52dcbf21..00764d15 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -76,10 +76,11 @@ pub fn insertRule(self: *CSSStyleSheet, rule: []const u8, maybe_index: ?u32, pag const index = maybe_index orelse 0; var it = Parser.parseStylesheet(rule); const parsed_rule = it.next() orelse { - const trimmed = std.mem.trimLeft(u8, rule, &std.ascii.whitespace); - if (std.mem.startsWith(u8, trimmed, "@")) { - // At-rules (like @keyframes) are currently skipped by the parser. - // Returning the index simulates successful insertion without crashing. + if (it.has_skipped_at_rule) { + // Lightpanda currently skips at-rules (e.g., @keyframes, @media) in its + // CSS parser. To prevent JS apps (like Expo/Reanimated) from crashing + // during initialization, we simulate a successful insertion by returning + // the requested index. return index; } return error.SyntaxError; From fd96cd6eb9384988410bbeaa3617acc26ae687d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Tue, 24 Mar 2026 09:20:21 +0900 Subject: [PATCH 3/4] chore(css): log unimplemented at-rules in insertRule --- src/browser/webapi/css/CSSStyleSheet.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index 00764d15..dde83529 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const log = @import("../../../log.zig"); const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); const Element = @import("../Element.zig"); @@ -77,6 +78,7 @@ pub fn insertRule(self: *CSSStyleSheet, rule: []const u8, maybe_index: ?u32, pag var it = Parser.parseStylesheet(rule); const parsed_rule = it.next() orelse { if (it.has_skipped_at_rule) { + log.debug(.not_implemented, "CSSStyleSheet.insertRule", .{}); // Lightpanda currently skips at-rules (e.g., @keyframes, @media) in its // CSS parser. To prevent JS apps (like Expo/Reanimated) from crashing // during initialization, we simulate a successful insertion by returning From 0bfe00bbb7548f653acb8fc38960be396c77ff8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Arrufat?= Date: Tue, 24 Mar 2026 12:53:49 +0900 Subject: [PATCH 4/4] css: disallow multiple rules in insertRule --- src/browser/tests/css/stylesheet.html | 18 ++++++++++++++++++ src/browser/webapi/css/CSSStyleSheet.zig | 2 ++ 2 files changed, 20 insertions(+) diff --git a/src/browser/tests/css/stylesheet.html b/src/browser/tests/css/stylesheet.html index af6ba4ef..d014dc45 100644 --- a/src/browser/tests/css/stylesheet.html +++ b/src/browser/tests/css/stylesheet.html @@ -480,6 +480,24 @@ } + +