diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 72a0a322..1b384efc 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -74,12 +74,77 @@ pub fn deinit(self: *StyleManager) void { self.page.releaseArena(self.arena); } -pub fn sheetAdded(self: *StyleManager, sheet: *CSSStyleSheet) !void { - const css_rules = sheet._css_rules orelse return; +fn parseSheet(self: *StyleManager, sheet: *CSSStyleSheet) !void { + if (sheet._css_rules) |css_rules| { + for (css_rules._rules.items) |rule| { + const style_rule = rule.is(CSSStyleRule) orelse continue; + try self.addRule(style_rule); + } + return; + } - for (css_rules._rules.items) |rule| { - const style_rule = rule.is(CSSStyleRule) orelse continue; - try self.addRule(style_rule); + const owner_node = sheet.getOwnerNode() orelse return; + if (owner_node.is(Element.Html.Style)) |style| { + const text = try style.asNode().getTextContentAlloc(self.arena); + var it = CssParser.parseStylesheet(text); + while (it.next()) |parsed_rule| { + try self.addRawRule(parsed_rule.selector, parsed_rule.block); + } + } +} + +fn addRawRule(self: *StyleManager, selector_text: []const u8, block_text: []const u8) !void { + if (selector_text.len == 0) return; + + var props = VisibilityProperties{}; + var it = CssParser.parseDeclarationsList(block_text); + while (it.next()) |decl| { + const name = decl.name; + const val = decl.value; + if (std.ascii.eqlIgnoreCase(name, "display")) { + props.display_none = std.ascii.eqlIgnoreCase(val, "none"); + } else if (std.ascii.eqlIgnoreCase(name, "visibility")) { + props.visibility_hidden = std.ascii.eqlIgnoreCase(val, "hidden") or std.ascii.eqlIgnoreCase(val, "collapse"); + } else if (std.ascii.eqlIgnoreCase(name, "opacity")) { + props.opacity_zero = std.ascii.eqlIgnoreCase(val, "0"); + } else if (std.ascii.eqlIgnoreCase(name, "pointer-events")) { + props.pointer_events_none = std.ascii.eqlIgnoreCase(val, "none"); + } + } + + if (!props.isRelevant()) return; + + const selectors = SelectorParser.parseList(self.arena, selector_text, self.page) catch return; + for (selectors) |selector| { + const rightmost = if (selector.segments.len > 0) selector.segments[selector.segments.len - 1].compound else selector.first; + const bucket_key = getBucketKey(rightmost) orelse continue; + const rule = VisibilityRule{ + .props = props, + .selector = selector, + .priority = (@as(u64, computeSpecificity(selector)) << 32) | @as(u64, self.next_doc_order), + }; + self.next_doc_order += 1; + + switch (bucket_key) { + .id => |id| { + const gop = try self.id_rules.getOrPut(self.arena, id); + if (!gop.found_existing) gop.value_ptr.* = .{}; + try gop.value_ptr.append(self.arena, rule); + }, + .class => |class| { + const gop = try self.class_rules.getOrPut(self.arena, class); + if (!gop.found_existing) gop.value_ptr.* = .{}; + try gop.value_ptr.append(self.arena, rule); + }, + .tag => |tag| { + const gop = try self.tag_rules.getOrPut(self.arena, tag); + if (!gop.found_existing) gop.value_ptr.* = .{}; + try gop.value_ptr.append(self.arena, rule); + }, + .other => { + try self.other_rules.append(self.arena, rule); + }, + } } } @@ -123,8 +188,8 @@ fn rebuildIfDirty(self: *StyleManager) !void { const sheets = self.page.document._style_sheets orelse return; for (sheets._sheets.items) |sheet| { - self.sheetAdded(sheet) catch |err| { - log.err(.browser, "StyleManager sheetAdded", .{ .err = err }); + self.parseSheet(sheet) catch |err| { + log.err(.browser, "StyleManager parseSheet", .{ .err = err }); return err; }; } diff --git a/src/browser/webapi/css/CSSStyleSheet.zig b/src/browser/webapi/css/CSSStyleSheet.zig index 6a37ecb5..f46e35df 100644 --- a/src/browser/webapi/css/CSSStyleSheet.zig +++ b/src/browser/webapi/css/CSSStyleSheet.zig @@ -46,8 +46,17 @@ pub fn setDisabled(self: *CSSStyleSheet, disabled: bool) void { pub fn getCssRules(self: *CSSStyleSheet, page: *Page) !*CSSRuleList { if (self._css_rules) |rules| return rules; + const rules = try CSSRuleList.init(page); self._css_rules = rules; + + if (self.getOwnerNode()) |owner| { + if (owner.is(Element.Html.Style)) |style| { + const text = try style.asNode().getTextContentAlloc(page.call_arena); + try self.replaceSync(text, page); + } + } + return rules; } @@ -89,7 +98,7 @@ pub fn replace(self: *CSSStyleSheet, text: []const u8, page: *Page) !js.Promise return page.js.local.?.resolvePromise(self); } -pub fn replaceSync(self: *CSSStyleSheet, text: []const u8, page: *Page) !void { +pub fn replaceSync(self: *CSSStyleSheet, text: []const u8, page: *Page) anyerror!void { const rules = try self.getCssRules(page); rules.clear(); diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index fc52cf38..e6cac8c3 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -95,9 +95,6 @@ pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { const sheet = try CSSStyleSheet.initWithOwner(self.asElement(), page); self._sheet = sheet; - const text = try self.asNode().getTextContentAlloc(page.call_arena); - try sheet.replaceSync(text, page); - const sheets = try page.document.getStyleSheets(page); try sheets.add(sheet, page); @@ -106,9 +103,9 @@ pub fn getSheet(self: *Style, page: *Page) !?*CSSStyleSheet { pub fn styleAddedCallback(self: *Style, page: *Page) !void { // Force stylesheet initialization so rules are parsed immediately - if (self.getSheet(page) catch null) |sheet| { + if (self.getSheet(page) catch null) |_| { // Notify StyleManager about the new stylesheet - page._style_manager.sheetAdded(sheet) catch {}; + page._style_manager.sheetModified(); } // if we're planning on navigating to another page, don't trigger load event.