Skip to content

Commit

Permalink
CSS Fixes: light dark, color down-leveling bugs, implement minify for…
Browse files Browse the repository at this point in the history
… box-shadow (#17055)
  • Loading branch information
zackradisic authored Feb 5, 2025
1 parent 8634ee3 commit dcf0b71
Show file tree
Hide file tree
Showing 13 changed files with 692 additions and 51 deletions.
6 changes: 1 addition & 5 deletions packages/bun-native-plugin-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@
name = "bun-native-plugin"
description = "Rustified wrapper for writing native plugins for Bun."
license = "MIT"
version = "0.1.2"
version = "0.2.0"
edition = "2021"

[dependencies]
anyhow = "1.0.94"
# use local path in dev and publish to crates.io in prod
bun-macro = { path = "./bun-macro", version = "0.1.0" }
napi = { version = "2.14.1", default-features = false, features = ["napi4"] }

[features]
default = ["napi"]
napi = []

39 changes: 18 additions & 21 deletions packages/bun-native-plugin-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,10 @@ impl<'a> OnBeforeParse<'a> {
}
}

/// # Safety
/// This is unsafe as you must ensure that no other invocation of the plugin (or JS!)
/// simultaneously holds a mutable reference to the external.
///
/// Get the external object from the `OnBeforeParse` arguments.
///
/// The external object is set by the plugin definition inside of JS:
Expand Down Expand Up @@ -521,42 +525,35 @@ impl<'a> OnBeforeParse<'a> {
/// },
/// };
/// ```
pub unsafe fn external<T: 'static + Sync>(&self) -> PluginResult<Option<&'static T>> {
pub unsafe fn external<'b, T: 'static + Sync>(
&self,
from_raw: unsafe fn(*mut c_void) -> Option<&'b T>,
) -> PluginResult<Option<&'b T>> {
if unsafe { (*self.args_raw).external.is_null() } {
return Ok(None);
}

let external: *mut TaggedObject<T> =
unsafe { (*self.args_raw).external as *mut TaggedObject<T> };

unsafe {
if (*external).type_id != TypeId::of::<T>() {
return Err(Error::ExternalTypeMismatch);
}
let external = unsafe { from_raw((*self.args_raw).external as *mut _) };

Ok((*external).object.as_ref())
}
Ok(external)
}

/// The same as [`crate::bun_native_plugin::OnBeforeParse::external`], but returns a mutable reference.
///
/// This is unsafe as you must ensure that no other invocation of the plugin
/// # Safety
/// This is unsafe as you must ensure that no other invocation of the plugin (or JS!)
/// simultaneously holds a mutable reference to the external.
pub unsafe fn external_mut<T: 'static + Sync>(&mut self) -> PluginResult<Option<&mut T>> {
pub unsafe fn external_mut<'b, T: 'static + Sync>(
&mut self,
from_raw: unsafe fn(*mut c_void) -> Option<&'b mut T>,
) -> PluginResult<Option<&'b mut T>> {
if unsafe { (*self.args_raw).external.is_null() } {
return Ok(None);
}

let external: *mut TaggedObject<T> =
unsafe { (*self.args_raw).external as *mut TaggedObject<T> };
let external = unsafe { from_raw((*self.args_raw).external as *mut _) };

unsafe {
if (*external).type_id != TypeId::of::<T>() {
return Err(Error::ExternalTypeMismatch);
}

Ok((*external).object.as_mut())
}
Ok(external)
}

/// Get the input source code for the current file.
Expand Down
6 changes: 5 additions & 1 deletion src/css/context.zig
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ pub const PropertyHandlerContext = struct {
};
}

pub fn addDarkRule(this: *@This(), allocator: Allocator, property: css.Property) void {
this.dark.append(allocator, property) catch bun.outOfMemory();
}

pub fn addLogicalRule(this: *@This(), allocator: Allocator, ltr: css.Property, rtl: css.Property) void {
this.ltr.append(allocator, ltr) catch unreachable;
this.rtl.append(allocator, rtl) catch unreachable;
Expand Down Expand Up @@ -171,7 +175,7 @@ pub const PropertyHandlerContext = struct {
.feature = MediaFeature{
.plain = .{
.name = .{ .standard = MediaFeatureId.@"prefers-color-scheme" },
.value = .{ .ident = .{ .v = "dark " } },
.value = .{ .ident = .{ .v = "dark" } },
},
},
},
Expand Down
8 changes: 8 additions & 0 deletions src/css/declaration.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ const FlexHandler = css.css_properties.flex.FlexHandler;
const AlignHandler = css.css_properties.@"align".AlignHandler;
const TransitionHandler = css.css_properties.transition.TransitionHandler;
const TransformHandler = css.css_properties.transform.TransformHandler;
const ColorSchemeHandler = css.css_properties.ui.ColorSchemeHandler;
const BoxShadowHandler = css.css_properties.box_shadow.BoxShadowHandler;
// const GridHandler = css.css_properties.g

/// A CSS declaration block.
Expand Down Expand Up @@ -347,6 +349,8 @@ pub const DeclarationHandler = struct {
font: FontHandler = .{},
inset: InsetHandler = .{},
transform: TransformHandler = .{},
box_shadow: BoxShadowHandler = .{},
color_scheme: ColorSchemeHandler = .{},
fallback: FallbackHandler = .{},
direction: ?css.css_properties.text.Direction,
decls: DeclarationList,
Expand Down Expand Up @@ -375,6 +379,8 @@ pub const DeclarationHandler = struct {
this.font.finalize(&this.decls, context);
this.inset.finalize(&this.decls, context);
this.transform.finalize(&this.decls, context);
this.box_shadow.finalize(&this.decls, context);
this.color_scheme.finalize(&this.decls, context);
this.fallback.finalize(&this.decls, context);
}

Expand All @@ -392,6 +398,8 @@ pub const DeclarationHandler = struct {
this.font.handleProperty(property, &this.decls, context) or
this.inset.handleProperty(property, &this.decls, context) or
this.transform.handleProperty(property, &this.decls, context) or
this.box_shadow.handleProperty(property, &this.decls, context) or
this.color_scheme.handleProperty(property, &this.decls, context) or
this.fallback.handleProperty(property, &this.decls, context);
}

Expand Down
143 changes: 142 additions & 1 deletion src/css/properties/box_shadow.zig
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ const DashedIdent = css.css_values.ident.DashedIdent;
const Image = css.css_values.image.Image;
const CssColor = css.css_values.color.CssColor;
const Ratio = css.css_values.ratio.Ratio;
const Length = css.css_values.length.LengthValue;
const Length = css.css_values.length.Length;
const Rect = css.css_values.rect.Rect;
const NumberOrPercentage = css.css_values.percentage.NumberOrPercentage;

const VendorPrefix = css.VendorPrefix;
const Property = css.Property;
const PropertyId = css.PropertyId;
const Feature = css.prefixes.Feature;

/// A value for the [box-shadow](https://drafts.csswg.org/css-backgrounds/#box-shadow) property.
pub const BoxShadow = struct {
/// The color of the box shadow.
Expand Down Expand Up @@ -128,4 +133,140 @@ pub const BoxShadow = struct {
pub fn eql(lhs: *const @This(), rhs: *const @This()) bool {
return css.implementEql(@This(), lhs, rhs);
}

pub fn isCompatible(this: *const @This(), browsers: css.targets.Browsers) bool {
return this.color.isCompatible(browsers) and
this.x_offset.isCompatible(browsers) and
this.y_offset.isCompatible(browsers) and
this.blur.isCompatible(browsers) and
this.spread.isCompatible(browsers);
}
};

pub const BoxShadowHandler = struct {
box_shadows: ?struct { SmallList(BoxShadow, 1), VendorPrefix } = null,
flushed: bool = false,

pub fn handleProperty(this: *@This(), property: *const Property, dest: *css.DeclarationList, context: *css.PropertyHandlerContext) bool {
switch (property.*) {
.@"box-shadow" => |*b| {
const box_shadows: *const SmallList(BoxShadow, 1) = &b.*[0];
const prefix: VendorPrefix = b.*[1];
if (this.box_shadows != null and context.targets.browsers != null and !box_shadows.isCompatible(context.targets.browsers.?)) {
this.flush(dest, context);
}

if (this.box_shadows) |*bxs| {
const val: *SmallList(BoxShadow, 1) = &bxs.*[0];
const prefixes: *VendorPrefix = &bxs.*[1];
if (!val.eql(box_shadows) and !prefixes.contains(prefix)) {
this.flush(dest, context);
this.box_shadows = .{
box_shadows.deepClone(context.allocator),
prefix,
};
} else {
val.* = box_shadows.deepClone(context.allocator);
prefixes.insert(prefix);
}
} else {
this.box_shadows = .{
box_shadows.deepClone(context.allocator),
prefix,
};
}
},
.unparsed => |unp| {
if (unp.property_id == .@"box-shadow") {
this.flush(dest, context);

var unparsed = unp.deepClone(context.allocator);
context.addUnparsedFallbacks(&unparsed);
dest.append(context.allocator, .{ .unparsed = unparsed }) catch bun.outOfMemory();
this.flushed = true;
} else return false;
},
else => return false,
}

return true;
}

pub fn finalize(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
this.flush(dest, context);
this.flushed = false;
}

pub fn flush(this: *@This(), dest: *css.DeclarationList, context: *css.PropertyHandlerContext) void {
if (this.box_shadows == null) return;

const box_shadows: SmallList(BoxShadow, 1), const prefixes2: VendorPrefix = bun.take(&this.box_shadows) orelse {
this.flushed = true;
return;
};

if (!this.flushed) {
const ColorFallbackKind = css.ColorFallbackKind;
var prefixes = context.targets.prefixes(prefixes2, Feature.box_shadow);
var fallbacks = ColorFallbackKind.empty();
for (box_shadows.slice()) |*shadow| {
fallbacks.insert(shadow.color.getNecessaryFallbacks(context.targets));
}

if (fallbacks.contains(ColorFallbackKind{ .rgb = true })) {
var rgb = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len());
rgb.setLen(box_shadows.len());
for (box_shadows.slice(), rgb.slice_mut()) |*input, *output| {
output.color = input.color.toRGB(context.allocator) orelse input.color.deepClone(context.allocator);
const fields = std.meta.fields(BoxShadow);
inline for (fields) |field| {
if (comptime std.mem.eql(u8, field.name, "color")) continue;
@field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator);
}
}

dest.append(context.allocator, .{ .@"box-shadow" = .{ rgb, prefixes } }) catch bun.outOfMemory();
if (prefixes.contains(VendorPrefix.NONE)) {
prefixes = VendorPrefix.NONE;
} else {
// Only output RGB for prefixed property (e.g. -webkit-box-shadow)
return;
}
}

if (fallbacks.contains(ColorFallbackKind.P3)) {
var p3 = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len());
p3.setLen(box_shadows.len());
for (box_shadows.slice(), p3.slice_mut()) |*input, *output| {
output.color = input.color.toP3(context.allocator) orelse input.color.deepClone(context.allocator);
const fields = std.meta.fields(BoxShadow);
inline for (fields) |field| {
if (comptime std.mem.eql(u8, field.name, "color")) continue;
@field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator);
}
}
dest.append(context.allocator, .{ .@"box-shadow" = .{ p3, VendorPrefix.NONE } }) catch bun.outOfMemory();
}

if (fallbacks.contains(ColorFallbackKind.LAB)) {
var lab = SmallList(BoxShadow, 1).initCapacity(context.allocator, box_shadows.len());
lab.setLen(box_shadows.len());
for (box_shadows.slice(), lab.slice_mut()) |*input, *output| {
output.color = input.color.toLAB(context.allocator) orelse input.color.deepClone(context.allocator);
const fields = std.meta.fields(BoxShadow);
inline for (fields) |field| {
if (comptime std.mem.eql(u8, field.name, "color")) continue;
@field(output, field.name) = css.generic.deepClone(field.type, &@field(input, field.name), context.allocator);
}
}
dest.append(context.allocator, .{ .@"box-shadow" = .{ lab, VendorPrefix.NONE } }) catch bun.outOfMemory();
} else {
dest.append(context.allocator, .{ .@"box-shadow" = .{ box_shadows, prefixes } }) catch bun.outOfMemory();
}
} else {
dest.append(context.allocator, .{ .@"box-shadow" = .{ box_shadows, prefixes2 } }) catch bun.outOfMemory();
}

this.flushed = true;
}
};
5 changes: 2 additions & 3 deletions src/css/properties/custom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -859,13 +859,12 @@ pub const UnresolvedColor = union(enum) {
const dark: *const TokenList = &ld.dark;

if (!dest.targets.isCompatible(.light_dark)) {
// TODO(zack): lightningcss -> buncss
try dest.writeStr("var(--lightningcss-light)");
try dest.writeStr("var(--buncss-light");
try dest.delim(',', false);
try light.toCss(W, dest, is_custom_property);
try dest.writeChar(')');
try dest.whitespace();
try dest.writeStr("var(--lightningcss-dark");
try dest.writeStr("var(--buncss-dark");
try dest.delim(',', false);
try dark.toCss(W, dest, is_custom_property);
return dest.writeChar(')');
Expand Down
8 changes: 5 additions & 3 deletions src/css/properties/generate_properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1755,9 +1755,9 @@ generateCode({
// "view-transition-name": {
// ty: "CustomIdent",
// },
// "color-scheme": {
// ty: "ColorScheme",
// },
"color-scheme": {
ty: "ColorScheme",
},
});

function prelude() {
Expand All @@ -1781,6 +1781,8 @@ const CustomProperty = css.css_properties.custom.CustomProperty;
const Targets = css.targets.Targets;
const Feature = css.prefixes.Feature;
const ColorScheme = css.css_properties.ui.ColorScheme;
const TransformList = css.css_properties.transform.TransformList;
const TransformStyle = css.css_properties.transform.TransformStyle;
const TransformBox = css.css_properties.transform.TransformBox;
Expand Down
Loading

0 comments on commit dcf0b71

Please sign in to comment.