mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-07-30 09:16:07 -04:00
Dedupe PointLIst and TransformList
Add an ugly mixin. Ensures the two implementations are the same.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const js = @import("../../js/js.zig");
|
||||
const Frame = @import("../../Frame.zig");
|
||||
@@ -24,6 +25,7 @@ const Page = @import("../../Page.zig");
|
||||
const DOMPoint = @import("../DOMPoint.zig");
|
||||
const DOMPointReadOnly = @import("../DOMPointReadOnly.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const reflected_list = @import("reflected_list.zig");
|
||||
|
||||
const PointList = @This();
|
||||
|
||||
@@ -35,6 +37,16 @@ _snapshot: std.ArrayList(u8) = .empty,
|
||||
_items: std.ArrayList(*DOMPoint) = .empty,
|
||||
_retired: std.ArrayList(*DOMPoint) = .empty,
|
||||
|
||||
const M = reflected_list.Mixin(PointList, DOMPoint, .{
|
||||
.attrName = attrName,
|
||||
.parse = parse,
|
||||
.writeItem = writeItem,
|
||||
.prepareItem = prepareItem,
|
||||
.attach = attach,
|
||||
.detachItem = detachItem,
|
||||
.releaseItem = releaseItem,
|
||||
});
|
||||
|
||||
pub const Kind = enum { base, animated };
|
||||
|
||||
pub const Key = struct {
|
||||
@@ -61,114 +73,19 @@ pub fn getOrCreate(element: *Element, kind: Kind, frame: *Frame) !*PointList {
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *PointList, page: *Page) void {
|
||||
for (self._items.items) |point| {
|
||||
point._proto.detach(self);
|
||||
point._proto.releaseRef(page);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
self.releaseRetired(page);
|
||||
}
|
||||
pub const deinit = M.deinit;
|
||||
pub const getLength = M.getLength;
|
||||
pub const getNumberOfItems = M.getNumberOfItems;
|
||||
pub const clear = M.clear;
|
||||
pub const initialize = M.initialize;
|
||||
pub const getItem = M.getItem;
|
||||
pub const insertItemBefore = M.insertItemBefore;
|
||||
pub const replaceItem = M.replaceItem;
|
||||
pub const removeItem = M.removeItem;
|
||||
pub const appendItem = M.appendItem;
|
||||
|
||||
pub fn getLength(self: *PointList, frame: *Frame) !u32 {
|
||||
try self.sync(frame);
|
||||
return @intCast(self._items.items.len);
|
||||
}
|
||||
|
||||
pub fn getNumberOfItems(self: *PointList, frame: *Frame) !u32 {
|
||||
return self.getLength(frame);
|
||||
}
|
||||
|
||||
pub fn clear(self: *PointList, frame: *Frame) !void {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
try self.retireAll(frame);
|
||||
try self.setAttribute(&.{}, frame);
|
||||
}
|
||||
|
||||
pub fn initialize(self: *PointList, item: *DOMPoint, frame: *Frame) !*DOMPoint {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared._proto.releaseRef(frame._page);
|
||||
|
||||
try self.retireAll(frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, 1);
|
||||
try self.setAttribute(&.{prepared}, frame);
|
||||
self._items.appendAssumeCapacity(prepared);
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn getItem(self: *PointList, index: u32, frame: *Frame) !*DOMPoint {
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
return self._items.items[index];
|
||||
}
|
||||
|
||||
pub fn insertItemBefore(self: *PointList, item: *DOMPoint, index: u32, frame: *Frame) !*DOMPoint {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared._proto.releaseRef(frame._page);
|
||||
const at = @min(@as(usize, index), self._items.items.len);
|
||||
const next = try frame.local_arena.alloc(*DOMPoint, self._items.items.len + 1);
|
||||
@memcpy(next[0..at], self._items.items[0..at]);
|
||||
next[at] = prepared;
|
||||
@memcpy(next[at + 1 ..], self._items.items[at..]);
|
||||
|
||||
try self._items.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
self._items.insertAssumeCapacity(at, prepared);
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn replaceItem(self: *PointList, item: *DOMPoint, index: u32, frame: *Frame) !*DOMPoint {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared._proto.releaseRef(frame._page);
|
||||
const next = try frame.local_arena.dupe(*DOMPoint, self._items.items);
|
||||
next[index] = prepared;
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
const replaced = self._items.items[index];
|
||||
replaced._proto.detach(self);
|
||||
self._retired.appendAssumeCapacity(replaced);
|
||||
self._items.items[index] = prepared;
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn removeItem(self: *PointList, index: u32, frame: *Frame) !*DOMPoint {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
|
||||
const next = try frame.local_arena.alloc(*DOMPoint, self._items.items.len - 1);
|
||||
@memcpy(next[0..index], self._items.items[0..index]);
|
||||
@memcpy(next[index..], self._items.items[index + 1 ..]);
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
const removed = self._items.orderedRemove(index);
|
||||
removed._proto.detach(self);
|
||||
self._retired.appendAssumeCapacity(removed);
|
||||
return removed;
|
||||
}
|
||||
|
||||
pub fn appendItem(self: *PointList, item: *DOMPoint, frame: *Frame) !*DOMPoint {
|
||||
return self.insertItemBefore(item, std.math.maxInt(u32), frame);
|
||||
}
|
||||
|
||||
fn requireMutable(self: *const PointList) !void {
|
||||
if (self._read_only) return error.NoModificationAllowed;
|
||||
fn attrName(_: *const PointList) lp.String {
|
||||
return .wrap("points");
|
||||
}
|
||||
|
||||
fn prepareItem(_: *PointList, item: *DOMPoint, frame: *Frame) !*DOMPoint {
|
||||
@@ -189,6 +106,14 @@ fn attach(self: *PointList, point: *DOMPoint) void {
|
||||
}, self._read_only);
|
||||
}
|
||||
|
||||
fn detachItem(point: *DOMPoint, owner: *PointList) void {
|
||||
point._proto.detach(owner);
|
||||
}
|
||||
|
||||
fn releaseItem(point: *DOMPoint, page: *Page) void {
|
||||
point._proto.releaseRef(page);
|
||||
}
|
||||
|
||||
fn mutatePoint(
|
||||
context: *anyopaque,
|
||||
point: *DOMPointReadOnly,
|
||||
@@ -197,7 +122,7 @@ fn mutatePoint(
|
||||
) anyerror!void {
|
||||
const self: *PointList = @ptrCast(@alignCast(context));
|
||||
const frame = self._frame;
|
||||
try self.sync(frame);
|
||||
try M.sync(self, frame);
|
||||
|
||||
// An external attribute mutation detaches the old item during sync. The
|
||||
// caller still owns that DOMPoint identity, but it no longer mutates the list.
|
||||
@@ -220,43 +145,6 @@ fn mutatePoint(
|
||||
point.setCoordinateRaw(coordinate, value);
|
||||
}
|
||||
|
||||
fn sync(self: *PointList, frame: *Frame) !void {
|
||||
self.releaseRetired(frame._page);
|
||||
|
||||
const raw = self._element.getAttributeSafe(comptime .wrap("points")) orelse "";
|
||||
if (self._synced and std.mem.eql(u8, self._snapshot.items, raw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._synced = false;
|
||||
var parsed = parse(raw, frame) catch |err| switch (err) {
|
||||
error.SyntaxError => std.ArrayList(*DOMPoint).empty,
|
||||
else => return err,
|
||||
};
|
||||
errdefer for (parsed.items) |point| point._proto.releaseRef(frame._page);
|
||||
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, raw);
|
||||
try self.retireAll(frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, parsed.items.len);
|
||||
for (parsed.items) |point| {
|
||||
self._items.appendAssumeCapacity(point);
|
||||
self.attach(point);
|
||||
}
|
||||
parsed.clearRetainingCapacity();
|
||||
self._synced = true;
|
||||
}
|
||||
|
||||
// A retired item must outlive the operation that retired it: removeItem's
|
||||
// return value has no JS wrapper until the bridge wraps it after we return.
|
||||
// By the next operation, anything still reachable holds its own ref.
|
||||
fn releaseRetired(self: *PointList, page: *Page) void {
|
||||
for (self._retired.items) |point| {
|
||||
point._proto.releaseRef(page);
|
||||
}
|
||||
self._retired.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
fn parse(raw: []const u8, frame: *Frame) !std.ArrayList(*DOMPoint) {
|
||||
var scanner = NumberScanner{ .input = raw };
|
||||
var parsed: std.ArrayList(*DOMPoint) = .empty;
|
||||
@@ -276,24 +164,8 @@ fn parse(raw: []const u8, frame: *Frame) !std.ArrayList(*DOMPoint) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
fn retireAll(self: *PointList, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, self._items.items.len);
|
||||
for (self._items.items) |point| {
|
||||
point._proto.detach(self);
|
||||
self._retired.appendAssumeCapacity(point);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
fn setAttribute(self: *PointList, items: []const *DOMPoint, frame: *Frame) !void {
|
||||
var serialized: std.Io.Writer.Allocating = .init(frame.local_arena);
|
||||
const writer = &serialized.writer;
|
||||
for (items, 0..) |point, i| {
|
||||
if (i != 0) try writer.writeByte(' ');
|
||||
try writer.print("{d} {d}", .{ point._proto._x, point._proto._y });
|
||||
}
|
||||
try self.commitAttribute(serialized.written(), frame);
|
||||
fn writeItem(point: *DOMPoint, writer: *std.Io.Writer) !void {
|
||||
try writer.print("{d} {d}", .{ point._proto._x, point._proto._y });
|
||||
}
|
||||
|
||||
fn setAttributeWithOverride(
|
||||
@@ -311,15 +183,7 @@ fn setAttributeWithOverride(
|
||||
const y = if (i == index and coordinate == .y) value else point._proto._y;
|
||||
try writer.print("{d} {d}", .{ x, y });
|
||||
}
|
||||
try self.commitAttribute(serialized.written(), frame);
|
||||
}
|
||||
|
||||
fn commitAttribute(self: *PointList, serialized: []const u8, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._element.setAttributeSafe(comptime .wrap("points"), .wrap(serialized), frame);
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, serialized);
|
||||
self._synced = true;
|
||||
try M.commitAttribute(self, serialized.written(), frame);
|
||||
}
|
||||
|
||||
const NumberScanner = struct {
|
||||
|
||||
@@ -25,6 +25,7 @@ const Page = @import("../../Page.zig");
|
||||
const DOMMatrixReadOnly = @import("../DOMMatrixReadOnly.zig");
|
||||
const Element = @import("../Element.zig");
|
||||
const Transform = @import("Transform.zig");
|
||||
const reflected_list = @import("reflected_list.zig");
|
||||
|
||||
const TransformList = @This();
|
||||
|
||||
@@ -37,6 +38,16 @@ _snapshot: std.ArrayList(u8) = .empty,
|
||||
_items: std.ArrayList(*Transform) = .empty,
|
||||
_retired: std.ArrayList(*Transform) = .empty,
|
||||
|
||||
const M = reflected_list.Mixin(TransformList, Transform, .{
|
||||
.attrName = attrName,
|
||||
.parse = parse,
|
||||
.writeItem = writeItem,
|
||||
.prepareItem = prepareItem,
|
||||
.attach = attach,
|
||||
.detachItem = detachItem,
|
||||
.releaseItem = releaseItem,
|
||||
});
|
||||
|
||||
pub fn createForAttribute(element: *Element, attr_name: lp.String, read_only: bool, frame: *Frame) !*TransformList {
|
||||
return frame._factory.create(TransformList{
|
||||
._frame = frame,
|
||||
@@ -46,111 +57,20 @@ pub fn createForAttribute(element: *Element, attr_name: lp.String, read_only: bo
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *TransformList, page: *Page) void {
|
||||
for (self._items.items) |transform| {
|
||||
transform.detach(self);
|
||||
transform.releaseRef(page);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
self.releaseRetired(page);
|
||||
}
|
||||
|
||||
pub fn getLength(self: *TransformList, frame: *Frame) !u32 {
|
||||
try self.sync(frame);
|
||||
return @intCast(self._items.items.len);
|
||||
}
|
||||
|
||||
pub fn getNumberOfItems(self: *TransformList, frame: *Frame) !u32 {
|
||||
return self.getLength(frame);
|
||||
}
|
||||
|
||||
pub fn clear(self: *TransformList, frame: *Frame) !void {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
try self.retireAll(frame);
|
||||
try self.setAttribute(&.{}, frame);
|
||||
}
|
||||
|
||||
pub fn initialize(self: *TransformList, item: *Transform, frame: *Frame) !*Transform {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared.releaseRef(frame._page);
|
||||
|
||||
try self.retireAll(frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, 1);
|
||||
try self.setAttribute(&.{prepared}, frame);
|
||||
self._items.appendAssumeCapacity(prepared);
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn getItem(self: *TransformList, index: u32, frame: *Frame) !*Transform {
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
return self._items.items[index];
|
||||
}
|
||||
|
||||
pub fn insertItemBefore(self: *TransformList, item: *Transform, index: u32, frame: *Frame) !*Transform {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared.releaseRef(frame._page);
|
||||
const at = @min(@as(usize, index), self._items.items.len);
|
||||
const next = try frame.local_arena.alloc(*Transform, self._items.items.len + 1);
|
||||
@memcpy(next[0..at], self._items.items[0..at]);
|
||||
next[at] = prepared;
|
||||
@memcpy(next[at + 1 ..], self._items.items[at..]);
|
||||
|
||||
try self._items.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
self._items.insertAssumeCapacity(at, prepared);
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn replaceItem(self: *TransformList, item: *Transform, index: u32, frame: *Frame) !*Transform {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
const prepared = try self.prepareItem(item, frame);
|
||||
errdefer prepared.releaseRef(frame._page);
|
||||
const next = try frame.local_arena.dupe(*Transform, self._items.items);
|
||||
next[index] = prepared;
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
const replaced = self._items.items[index];
|
||||
replaced.detach(self);
|
||||
self._retired.appendAssumeCapacity(replaced);
|
||||
self._items.items[index] = prepared;
|
||||
self.attach(prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn removeItem(self: *TransformList, index: u32, frame: *Frame) !*Transform {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
const next = try frame.local_arena.alloc(*Transform, self._items.items.len - 1);
|
||||
@memcpy(next[0..index], self._items.items[0..index]);
|
||||
@memcpy(next[index..], self._items.items[index + 1 ..]);
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try self.setAttribute(next, frame);
|
||||
const removed = self._items.orderedRemove(index);
|
||||
removed.detach(self);
|
||||
self._retired.appendAssumeCapacity(removed);
|
||||
return removed;
|
||||
}
|
||||
|
||||
pub fn appendItem(self: *TransformList, item: *Transform, frame: *Frame) !*Transform {
|
||||
return self.insertItemBefore(item, std.math.maxInt(u32), frame);
|
||||
}
|
||||
pub const deinit = M.deinit;
|
||||
pub const getLength = M.getLength;
|
||||
pub const getNumberOfItems = M.getNumberOfItems;
|
||||
pub const clear = M.clear;
|
||||
pub const initialize = M.initialize;
|
||||
pub const getItem = M.getItem;
|
||||
pub const insertItemBefore = M.insertItemBefore;
|
||||
pub const replaceItem = M.replaceItem;
|
||||
pub const removeItem = M.removeItem;
|
||||
pub const appendItem = M.appendItem;
|
||||
|
||||
pub fn consolidate(self: *TransformList, frame: *Frame) !?*Transform {
|
||||
try self.requireMutable();
|
||||
try self.sync(frame);
|
||||
try M.requireMutable(self);
|
||||
try M.sync(self, frame);
|
||||
if (self._items.items.len == 0) return null;
|
||||
|
||||
var matrix = DOMMatrixReadOnly.identity();
|
||||
@@ -173,16 +93,16 @@ pub fn consolidate(self: *TransformList, frame: *Frame) !?*Transform {
|
||||
consolidated.acquireRef();
|
||||
errdefer consolidated.releaseRef(frame._page);
|
||||
|
||||
try self.retireAll(frame);
|
||||
try M.retireAll(self, frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, 1);
|
||||
try self.setAttribute(&.{consolidated}, frame);
|
||||
try M.setAttribute(self, &.{consolidated}, frame);
|
||||
self._items.appendAssumeCapacity(consolidated);
|
||||
self.attach(consolidated);
|
||||
attach(self, consolidated);
|
||||
return consolidated;
|
||||
}
|
||||
|
||||
fn requireMutable(self: *const TransformList) !void {
|
||||
if (self._read_only) return error.NoModificationAllowed;
|
||||
fn attrName(self: *const TransformList) lp.String {
|
||||
return self._attr_name;
|
||||
}
|
||||
|
||||
fn prepareItem(_: *TransformList, item: *Transform, frame: *Frame) !*Transform {
|
||||
@@ -198,10 +118,18 @@ fn attach(self: *TransformList, transform: *Transform) void {
|
||||
}, self._read_only);
|
||||
}
|
||||
|
||||
fn detachItem(transform: *Transform, owner: *TransformList) void {
|
||||
transform.detach(owner);
|
||||
}
|
||||
|
||||
fn releaseItem(transform: *Transform, page: *Page) void {
|
||||
transform.releaseRef(page);
|
||||
}
|
||||
|
||||
fn mutateTransform(context: *anyopaque, transform: *Transform, state: Transform.State) anyerror!void {
|
||||
const self: *TransformList = @ptrCast(@alignCast(context));
|
||||
const frame = self._frame;
|
||||
try self.sync(frame);
|
||||
try M.sync(self, frame);
|
||||
if (!transform.isAttachedTo(self)) {
|
||||
transform.applyStateRaw(state);
|
||||
return;
|
||||
@@ -213,41 +141,6 @@ fn mutateTransform(context: *anyopaque, transform: *Transform, state: Transform.
|
||||
transform.applyStateRaw(state);
|
||||
}
|
||||
|
||||
fn sync(self: *TransformList, frame: *Frame) !void {
|
||||
self.releaseRetired(frame._page);
|
||||
|
||||
const raw = self._element.getAttributeSafe(self._attr_name) orelse "";
|
||||
if (self._synced and std.mem.eql(u8, self._snapshot.items, raw)) return;
|
||||
|
||||
self._synced = false;
|
||||
var parsed = parse(raw, frame) catch |err| switch (err) {
|
||||
error.SyntaxError => std.ArrayList(*Transform).empty,
|
||||
else => return err,
|
||||
};
|
||||
errdefer for (parsed.items) |transform| transform.releaseRef(frame._page);
|
||||
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, raw);
|
||||
try self.retireAll(frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, parsed.items.len);
|
||||
for (parsed.items) |transform| {
|
||||
self._items.appendAssumeCapacity(transform);
|
||||
self.attach(transform);
|
||||
}
|
||||
parsed.clearRetainingCapacity();
|
||||
self._synced = true;
|
||||
}
|
||||
|
||||
// A retired item must outlive the operation that retired it: removeItem's
|
||||
// return value has no JS wrapper until the bridge wraps it after we return.
|
||||
// By the next operation, anything still reachable holds its own ref.
|
||||
fn releaseRetired(self: *TransformList, page: *Page) void {
|
||||
for (self._retired.items) |transform| {
|
||||
transform.releaseRef(page);
|
||||
}
|
||||
self._retired.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
fn parse(raw: []const u8, frame: *Frame) !std.ArrayList(*Transform) {
|
||||
var parsed: std.ArrayList(*Transform) = .empty;
|
||||
errdefer for (parsed.items) |transform| transform.releaseRef(frame._page);
|
||||
@@ -267,24 +160,8 @@ fn parse(raw: []const u8, frame: *Frame) !std.ArrayList(*Transform) {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
fn retireAll(self: *TransformList, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, self._items.items.len);
|
||||
for (self._items.items) |transform| {
|
||||
transform.detach(self);
|
||||
self._retired.appendAssumeCapacity(transform);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
fn setAttribute(self: *TransformList, items: []const *Transform, frame: *Frame) !void {
|
||||
var serialized: std.Io.Writer.Allocating = .init(frame.local_arena);
|
||||
const writer = &serialized.writer;
|
||||
for (items, 0..) |transform, i| {
|
||||
if (i != 0) try writer.writeByte(' ');
|
||||
try Transform.writeState(transform.getState(), writer);
|
||||
}
|
||||
try self.commitAttribute(serialized.written(), frame);
|
||||
fn writeItem(transform: *Transform, writer: *std.Io.Writer) !void {
|
||||
try Transform.writeState(transform.getState(), writer);
|
||||
}
|
||||
|
||||
fn setAttributeWithOverride(self: *TransformList, index: usize, state: Transform.State, frame: *Frame) !void {
|
||||
@@ -294,15 +171,7 @@ fn setAttributeWithOverride(self: *TransformList, index: usize, state: Transform
|
||||
if (i != 0) try writer.writeByte(' ');
|
||||
try Transform.writeState(if (i == index) state else transform.getState(), writer);
|
||||
}
|
||||
try self.commitAttribute(serialized.written(), frame);
|
||||
}
|
||||
|
||||
fn commitAttribute(self: *TransformList, serialized: []const u8, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._element.setAttributeSafe(self._attr_name, .wrap(serialized), frame);
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, serialized);
|
||||
self._synced = true;
|
||||
try M.commitAttribute(self, serialized.written(), frame);
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
|
||||
226
src/browser/webapi/svg/reflected_list.zig
Normal file
226
src/browser/webapi/svg/reflected_list.zig
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Shared machinery for the attribute-reflecting SVG list interfaces whose
|
||||
//! items are live, ref-counted objects (SVGPointList, SVGTransformList):
|
||||
//! attribute-snapshot sync, the retire/release item lifetime protocol, and
|
||||
//! the DOM list operations. The concrete list supplies the item specifics
|
||||
//! via `hooks` and re-exports the operations as decl aliases.
|
||||
//!
|
||||
//! StringList reflects an attribute too but is deliberately not built on
|
||||
//! this: its items are plain slices (no ref counting, no attach/retire) and
|
||||
//! its commit path is list-authoritative rather than reparse-based.
|
||||
//!
|
||||
//! `List` must carry the `_frame`, `_element`, `_read_only`, `_synced`,
|
||||
//! `_snapshot`, `_items` and `_retired` fields. `hooks` must provide:
|
||||
//! attrName(*const List) String
|
||||
//! parse([]const u8, *Frame) !std.ArrayList(*Item) (SyntaxError -> empty list)
|
||||
//! writeItem(*Item, *std.Io.Writer) !void
|
||||
//! prepareItem(*List, *Item, *Frame) !*Item
|
||||
//! attach(*List, *Item) void
|
||||
//! detachItem(*Item, *List) void
|
||||
//! releaseItem(*Item, *Page) void
|
||||
//!
|
||||
//! The operations keep a strict commit order: reserve list capacity, write
|
||||
//! the attribute, then apply the infallible list update — so a failed
|
||||
//! setAttribute never leaves the mirror out of step with the attribute.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const Frame = @import("../../Frame.zig");
|
||||
const Page = @import("../../Page.zig");
|
||||
|
||||
pub fn Mixin(comptime List: type, comptime Item: type, comptime hooks: anytype) type {
|
||||
return struct {
|
||||
pub fn deinit(self: *List, page: *Page) void {
|
||||
for (self._items.items) |item| {
|
||||
hooks.detachItem(item, self);
|
||||
hooks.releaseItem(item, page);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
releaseRetired(self, page);
|
||||
}
|
||||
|
||||
pub fn getLength(self: *List, frame: *Frame) !u32 {
|
||||
try sync(self, frame);
|
||||
return @intCast(self._items.items.len);
|
||||
}
|
||||
|
||||
pub fn getNumberOfItems(self: *List, frame: *Frame) !u32 {
|
||||
return getLength(self, frame);
|
||||
}
|
||||
|
||||
pub fn clear(self: *List, frame: *Frame) !void {
|
||||
try requireMutable(self);
|
||||
try sync(self, frame);
|
||||
try retireAll(self, frame);
|
||||
try setAttribute(self, &.{}, frame);
|
||||
}
|
||||
|
||||
pub fn initialize(self: *List, item: *Item, frame: *Frame) !*Item {
|
||||
try requireMutable(self);
|
||||
try sync(self, frame);
|
||||
|
||||
const prepared = try hooks.prepareItem(self, item, frame);
|
||||
errdefer hooks.releaseItem(prepared, frame._page);
|
||||
|
||||
try retireAll(self, frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, 1);
|
||||
try setAttribute(self, &.{prepared}, frame);
|
||||
self._items.appendAssumeCapacity(prepared);
|
||||
hooks.attach(self, prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn getItem(self: *List, index: u32, frame: *Frame) !*Item {
|
||||
try sync(self, frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
return self._items.items[index];
|
||||
}
|
||||
|
||||
pub fn insertItemBefore(self: *List, item: *Item, index: u32, frame: *Frame) !*Item {
|
||||
try requireMutable(self);
|
||||
try sync(self, frame);
|
||||
|
||||
const prepared = try hooks.prepareItem(self, item, frame);
|
||||
errdefer hooks.releaseItem(prepared, frame._page);
|
||||
const at = @min(@as(usize, index), self._items.items.len);
|
||||
const next = try frame.local_arena.alloc(*Item, self._items.items.len + 1);
|
||||
@memcpy(next[0..at], self._items.items[0..at]);
|
||||
next[at] = prepared;
|
||||
@memcpy(next[at + 1 ..], self._items.items[at..]);
|
||||
|
||||
try self._items.ensureUnusedCapacity(frame.arena, 1);
|
||||
try setAttribute(self, next, frame);
|
||||
self._items.insertAssumeCapacity(at, prepared);
|
||||
hooks.attach(self, prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn replaceItem(self: *List, item: *Item, index: u32, frame: *Frame) !*Item {
|
||||
try requireMutable(self);
|
||||
try sync(self, frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
|
||||
const prepared = try hooks.prepareItem(self, item, frame);
|
||||
errdefer hooks.releaseItem(prepared, frame._page);
|
||||
const next = try frame.local_arena.dupe(*Item, self._items.items);
|
||||
next[index] = prepared;
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try setAttribute(self, next, frame);
|
||||
const replaced = self._items.items[index];
|
||||
hooks.detachItem(replaced, self);
|
||||
self._retired.appendAssumeCapacity(replaced);
|
||||
self._items.items[index] = prepared;
|
||||
hooks.attach(self, prepared);
|
||||
return prepared;
|
||||
}
|
||||
|
||||
pub fn removeItem(self: *List, index: u32, frame: *Frame) !*Item {
|
||||
try requireMutable(self);
|
||||
try sync(self, frame);
|
||||
if (index >= self._items.items.len) return error.IndexSizeError;
|
||||
|
||||
const next = try frame.local_arena.alloc(*Item, self._items.items.len - 1);
|
||||
@memcpy(next[0..index], self._items.items[0..index]);
|
||||
@memcpy(next[index..], self._items.items[index + 1 ..]);
|
||||
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, 1);
|
||||
try setAttribute(self, next, frame);
|
||||
const removed = self._items.orderedRemove(index);
|
||||
hooks.detachItem(removed, self);
|
||||
self._retired.appendAssumeCapacity(removed);
|
||||
return removed;
|
||||
}
|
||||
|
||||
pub fn appendItem(self: *List, item: *Item, frame: *Frame) !*Item {
|
||||
return insertItemBefore(self, item, std.math.maxInt(u32), frame);
|
||||
}
|
||||
|
||||
pub fn requireMutable(self: *const List) !void {
|
||||
if (self._read_only) return error.NoModificationAllowed;
|
||||
}
|
||||
|
||||
pub fn sync(self: *List, frame: *Frame) !void {
|
||||
releaseRetired(self, frame._page);
|
||||
|
||||
const raw = self._element.getAttributeSafe(hooks.attrName(self)) orelse "";
|
||||
if (self._synced and std.mem.eql(u8, self._snapshot.items, raw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._synced = false;
|
||||
var parsed = hooks.parse(raw, frame) catch |err| switch (err) {
|
||||
error.SyntaxError => std.ArrayList(*Item).empty,
|
||||
else => return err,
|
||||
};
|
||||
errdefer for (parsed.items) |item| hooks.releaseItem(item, frame._page);
|
||||
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, raw);
|
||||
try retireAll(self, frame);
|
||||
try self._items.ensureTotalCapacity(frame.arena, parsed.items.len);
|
||||
for (parsed.items) |item| {
|
||||
self._items.appendAssumeCapacity(item);
|
||||
hooks.attach(self, item);
|
||||
}
|
||||
parsed.clearRetainingCapacity();
|
||||
self._synced = true;
|
||||
}
|
||||
|
||||
// A retired item must outlive the operation that retired it:
|
||||
// removeItem's return value has no JS wrapper until the bridge wraps
|
||||
// it after we return. By the next operation, anything still reachable
|
||||
// holds its own ref.
|
||||
pub fn releaseRetired(self: *List, page: *Page) void {
|
||||
for (self._retired.items) |item| {
|
||||
hooks.releaseItem(item, page);
|
||||
}
|
||||
self._retired.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn retireAll(self: *List, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._retired.ensureUnusedCapacity(frame.arena, self._items.items.len);
|
||||
for (self._items.items) |item| {
|
||||
hooks.detachItem(item, self);
|
||||
self._retired.appendAssumeCapacity(item);
|
||||
}
|
||||
self._items.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn setAttribute(self: *List, items: []const *Item, frame: *Frame) !void {
|
||||
var serialized: std.Io.Writer.Allocating = .init(frame.local_arena);
|
||||
const writer = &serialized.writer;
|
||||
for (items, 0..) |item, i| {
|
||||
if (i != 0) try writer.writeByte(' ');
|
||||
try hooks.writeItem(item, writer);
|
||||
}
|
||||
try commitAttribute(self, serialized.written(), frame);
|
||||
}
|
||||
|
||||
pub fn commitAttribute(self: *List, serialized: []const u8, frame: *Frame) !void {
|
||||
self._synced = false;
|
||||
try self._element.setAttributeSafe(hooks.attrName(self), .wrap(serialized), frame);
|
||||
self._snapshot.clearRetainingCapacity();
|
||||
try self._snapshot.appendSlice(frame.arena, serialized);
|
||||
self._synced = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user