diff --git a/src/browser/webapi/element/svg/G.zig b/src/browser/webapi/element/svg/G.zig index 26a264173..93c3d2a94 100644 --- a/src/browser/webapi/element/svg/G.zig +++ b/src/browser/webapi/element/svg/G.zig @@ -1,4 +1,4 @@ -// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // // Francis Bouvier // Pierre Tachoire diff --git a/src/browser/webapi/svg/Angle.zig b/src/browser/webapi/svg/Angle.zig index f9f7011d8..4cedaf54e 100644 --- a/src/browser/webapi/svg/Angle.zig +++ b/src/browser/webapi/svg/Angle.zig @@ -21,11 +21,14 @@ const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Frame = @import("../../Frame.zig"); +const Page = @import("../../Page.zig"); const Element = @import("../Element.zig"); const String = lp.String; const Angle = @This(); +_rc: lp.RC = .{}, +_arena: std.mem.Allocator, _value: f64 = 0, _unit: Unit = .unspecified, _element: ?*Element = null, @@ -42,7 +45,23 @@ const Unit = enum(u16) { }; pub fn detached(frame: *Frame) !*Angle { - return frame._factory.create(Angle{}); + const arena = try frame._page.getArena(.tiny, "SVGAngle"); + errdefer frame._page.releaseArena(arena); + const self = try arena.create(Angle); + self.* = .{ ._arena = arena }; + return self; +} + +pub fn deinit(self: *Angle, page: *Page) void { + page.releaseArena(self._arena); +} + +pub fn acquireRef(self: *Angle) void { + self._rc.acquire(); +} + +pub fn releaseRef(self: *Angle, page: *Page) void { + self._rc.release(self, page); } pub fn getUnitType(self: *Angle) u16 { diff --git a/src/browser/webapi/svg/AnimatedEnumeration.zig b/src/browser/webapi/svg/AnimatedEnumeration.zig index 98c486006..4cba5a959 100644 --- a/src/browser/webapi/svg/AnimatedEnumeration.zig +++ b/src/browser/webapi/svg/AnimatedEnumeration.zig @@ -138,7 +138,7 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedEnume return gop.value_ptr.*; } -pub fn create( +fn create( element: *Element, attr_name: lp.String, entries: []const Entry, diff --git a/src/browser/webapi/svg/AnimatedLength.zig b/src/browser/webapi/svg/AnimatedLength.zig index 8d327ba07..eb540ce56 100644 --- a/src/browser/webapi/svg/AnimatedLength.zig +++ b/src/browser/webapi/svg/AnimatedLength.zig @@ -208,16 +208,7 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedLengt return gop.value_ptr.*; } -pub fn create(element: *Element, attr_name: lp.String, direction: Length.Direction, frame: *Frame) !*AnimatedLength { - const base_val = try Length.reflected(element, attr_name, direction, false, frame); - const anim_val = try Length.reflected(element, attr_name, direction, true, frame); - return frame._factory.create(AnimatedLength{ - ._base_val = base_val, - ._anim_val = anim_val, - }); -} - -pub fn createConfigured( +fn createConfigured( element: *Element, attr_name: lp.String, direction: Length.Direction, diff --git a/src/browser/webapi/svg/AnimatedPreserveAspectRatio.zig b/src/browser/webapi/svg/AnimatedPreserveAspectRatio.zig index 2fcefcea9..5082f60e6 100644 --- a/src/browser/webapi/svg/AnimatedPreserveAspectRatio.zig +++ b/src/browser/webapi/svg/AnimatedPreserveAspectRatio.zig @@ -39,7 +39,7 @@ pub fn getOrCreate(element: *Element, frame: *Frame) !*AnimatedPreserveAspectRat return gop.value_ptr.*; } -pub fn create(element: *Element, frame: *Frame) !*AnimatedPreserveAspectRatio { +fn create(element: *Element, frame: *Frame) !*AnimatedPreserveAspectRatio { const base_val = try PreserveAspectRatio.create(element, false, frame); const anim_val = try PreserveAspectRatio.create(element, true, frame); return frame._factory.create(AnimatedPreserveAspectRatio{ diff --git a/src/browser/webapi/svg/AnimatedTransformList.zig b/src/browser/webapi/svg/AnimatedTransformList.zig index 4c6f55988..78a53a5d2 100644 --- a/src/browser/webapi/svg/AnimatedTransformList.zig +++ b/src/browser/webapi/svg/AnimatedTransformList.zig @@ -1,9 +1,20 @@ // Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // +// Francis Bouvier +// Pierre Tachoire +// // This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); @@ -50,10 +61,6 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*AnimatedTrans return gop.value_ptr.*; } -pub fn create(element: *Element, frame: *Frame) !*AnimatedTransformList { - return createForAttribute(element, comptime .wrap("transform"), frame); -} - pub fn createForAttribute(element: *Element, attr_name: lp.String, frame: *Frame) !*AnimatedTransformList { const base_val = try TransformList.createForAttribute(element, attr_name, false, frame); const anim_val = try TransformList.createForAttribute(element, attr_name, true, frame); diff --git a/src/browser/webapi/svg/Length.zig b/src/browser/webapi/svg/Length.zig index f3cc825f8..ede68e82a 100644 --- a/src/browser/webapi/svg/Length.zig +++ b/src/browser/webapi/svg/Length.zig @@ -21,11 +21,17 @@ const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Frame = @import("../../Frame.zig"); +const Page = @import("../../Page.zig"); const Element = @import("../Element.zig"); const String = lp.String; const Length = @This(); +// A reflected length is created with one ref held by the frame's animated +// lookup cache, so a JS wrapper being collected can never free it. Only a +// detached length (own arena, zero initial refs) dies with its last wrapper. +_rc: lp.RC = .init(1), +_arena: ?std.mem.Allocator = null, _value: f64 = 0, _unit: Unit = .number, _element: ?*Element = null, @@ -58,16 +64,23 @@ pub const Unit = enum(u16) { const MAX_ANCESTOR_DEPTH = 32; pub fn detached(frame: *Frame) !*Length { - return frame._factory.create(Length{}); + const arena = try frame._page.getArena(.tiny, "SVGLength"); + errdefer frame._page.releaseArena(arena); + const self = try arena.create(Length); + self.* = .{ ._rc = .{}, ._arena = arena }; + return self; } -pub fn reflected(element: *Element, attr_name: String, direction: Direction, read_only: bool, frame: *Frame) !*Length { - return frame._factory.create(Length{ - ._element = element, - ._attr_name = attr_name, - ._direction = direction, - ._read_only = read_only, - }); +pub fn deinit(self: *Length, page: *Page) void { + page.releaseArena(self._arena.?); +} + +pub fn acquireRef(self: *Length) void { + self._rc.acquire(); +} + +pub fn releaseRef(self: *Length, page: *Page) void { + self._rc.release(self, page); } pub fn reflectedConfigured( diff --git a/src/browser/webapi/svg/Number.zig b/src/browser/webapi/svg/Number.zig index 75aea8663..3183f2f99 100644 --- a/src/browser/webapi/svg/Number.zig +++ b/src/browser/webapi/svg/Number.zig @@ -17,15 +17,36 @@ // along with this program. If not, see . const std = @import("std"); +const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Frame = @import("../../Frame.zig"); +const Page = @import("../../Page.zig"); const Number = @This(); + +_rc: lp.RC = .{}, +_arena: std.mem.Allocator, _value: f32 = 0, pub fn detached(frame: *Frame) !*Number { - return frame._factory.create(Number{}); + const arena = try frame._page.getArena(.tiny, "SVGNumber"); + errdefer frame._page.releaseArena(arena); + const self = try arena.create(Number); + self.* = .{ ._arena = arena }; + return self; +} + +pub fn deinit(self: *Number, page: *Page) void { + page.releaseArena(self._arena); +} + +pub fn acquireRef(self: *Number) void { + self._rc.acquire(); +} + +pub fn releaseRef(self: *Number, page: *Page) void { + self._rc.release(self, page); } pub fn getValue(self: *const Number) f32 { diff --git a/src/browser/webapi/svg/PathData.zig b/src/browser/webapi/svg/PathData.zig index 9031ede7e..38e50367f 100644 --- a/src/browser/webapi/svg/PathData.zig +++ b/src/browser/webapi/svg/PathData.zig @@ -15,6 +15,7 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . + const std = @import("std"); const Allocator = std.mem.Allocator; @@ -138,12 +139,12 @@ pub const Path = struct { try self.segments.append(allocator, .{ .line = .{ .start = start, .end = end } }); } - pub fn appendQuadratic(self: *Path, segment: Quadratic, allocator: Allocator) !void { + fn appendQuadratic(self: *Path, segment: Quadratic, allocator: Allocator) !void { if (self.first_point == null) self.first_point = segment.start; try self.segments.append(allocator, .{ .quadratic = segment }); } - pub fn appendCubic(self: *Path, segment: Cubic, allocator: Allocator) !void { + fn appendCubic(self: *Path, segment: Cubic, allocator: Allocator) !void { if (self.first_point == null) self.first_point = segment.start; try self.segments.append(allocator, .{ .cubic = segment }); } diff --git a/src/browser/webapi/svg/PointList.zig b/src/browser/webapi/svg/PointList.zig index 6c9a645a4..0d179b91c 100644 --- a/src/browser/webapi/svg/PointList.zig +++ b/src/browser/webapi/svg/PointList.zig @@ -1,9 +1,20 @@ // Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // +// Francis Bouvier +// Pierre Tachoire +// // This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); diff --git a/src/browser/webapi/svg/StringList.zig b/src/browser/webapi/svg/StringList.zig index ccd217063..7856a7696 100644 --- a/src/browser/webapi/svg/StringList.zig +++ b/src/browser/webapi/svg/StringList.zig @@ -1,9 +1,20 @@ // Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // +// Francis Bouvier +// Pierre Tachoire +// // This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); @@ -14,7 +25,7 @@ const Element = @import("../Element.zig"); const StringList = @This(); -pub const Delimiter = enum { whitespace, comma }; +const Delimiter = enum { whitespace, comma }; _element: *Element, _attribute_name: lp.String, diff --git a/src/browser/webapi/svg/Transform.zig b/src/browser/webapi/svg/Transform.zig index 2ebe7dc02..1dabb2aad 100644 --- a/src/browser/webapi/svg/Transform.zig +++ b/src/browser/webapi/svg/Transform.zig @@ -66,9 +66,10 @@ pub const Attachment = struct { mutate: *const fn (*anyopaque, *Transform, State) anyerror!void, }; -// The transform owns the matrix arena even when no JS wrapper currently -// references `matrix`. Forwarding the transform's bridge lifetime keeps the -// stable SameObject pointer valid across garbage collections. +// The transform lives in the matrix arena and owns it even when no JS wrapper +// currently references `matrix`. Forwarding the transform's bridge lifetime +// keeps the stable SameObject pointer valid across garbage collections, and +// releasing the last ref frees the transform together with its matrix. pub fn acquireRef(self: *Transform) void { self._matrix._proto.acquireRef(); } @@ -80,7 +81,8 @@ pub fn releaseRef(self: *Transform, page: *Page) void { pub fn detached(frame: *Frame) !*Transform { const matrix = try DOMMatrix.create(RO.identity(), true, frame._page); errdefer matrix._proto.deinit(frame._page); - const self = try frame._factory.create(Transform{ ._matrix = matrix }); + const self = try matrix._proto._arena.create(Transform); + self.* = .{ ._matrix = matrix }; self.attachMatrix(); return self; } @@ -89,7 +91,8 @@ pub fn fromMatrix(init: ?DOMMatrix2DInit, frame: *Frame) !*Transform { const parsed = try fixup2D(init orelse .{}); const matrix = try DOMMatrix.create(parsed.m, true, frame._page); errdefer matrix._proto.deinit(frame._page); - const self = try frame._factory.create(Transform{ ._matrix = matrix }); + const self = try matrix._proto._arena.create(Transform); + self.* = .{ ._matrix = matrix }; self.attachMatrix(); return self; } @@ -106,13 +109,14 @@ pub fn fromParsed(parsed: RO.ParsedTransform, frame: *Frame) !*Transform { }; const matrix = try DOMMatrix.create(parsed.matrix, parsed.is_2d, frame._page); errdefer matrix._proto.deinit(frame._page); - const self = try frame._factory.create(Transform{ + const self = try matrix._proto._arena.create(Transform); + self.* = .{ ._type = typ, ._angle = if (typ >= 4) parsed.values[0] else 0, ._cx = if (typ == 4 and parsed.count == 3) parsed.values[1] else 0, ._cy = if (typ == 4 and parsed.count == 3) parsed.values[2] else 0, ._matrix = matrix, - }); + }; self.attachMatrix(); return self; } @@ -121,13 +125,14 @@ pub fn clone(self: *const Transform, frame: *Frame) !*Transform { const current = self.getState(); const matrix = try DOMMatrix.create(current.matrix, current.is_2d, frame._page); errdefer matrix._proto.deinit(frame._page); - const cloned = try frame._factory.create(Transform{ + const cloned = try matrix._proto._arena.create(Transform); + cloned.* = .{ ._type = current.typ, ._angle = current.angle, ._cx = current.cx, ._cy = current.cy, ._matrix = matrix, - }); + }; cloned.attachMatrix(); return cloned; } diff --git a/src/browser/webapi/svg/TransformList.zig b/src/browser/webapi/svg/TransformList.zig index c882f0690..15a8ae9a5 100644 --- a/src/browser/webapi/svg/TransformList.zig +++ b/src/browser/webapi/svg/TransformList.zig @@ -1,9 +1,20 @@ // Copyright (C) 2023-2026 Lightpanda (Selecy SAS) // +// Francis Bouvier +// Pierre Tachoire +// // This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . const std = @import("std"); const lp = @import("lightpanda"); @@ -26,10 +37,6 @@ _snapshot: std.ArrayList(u8) = .empty, _items: std.ArrayList(*Transform) = .empty, _retired: std.ArrayList(*Transform) = .empty, -pub fn create(element: *Element, read_only: bool, frame: *Frame) !*TransformList { - return createForAttribute(element, comptime .wrap("transform"), read_only, frame); -} - pub fn createForAttribute(element: *Element, attr_name: lp.String, read_only: bool, frame: *Frame) !*TransformList { return frame._factory.create(TransformList{ ._frame = frame,