mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-12-15 15:58:57 +00:00
add TextTrackCue and VTTCue (for reddit)
This commit is contained in:
@@ -117,7 +117,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void
|
||||
|
||||
switch (target._type) {
|
||||
.node => |node| try self.dispatchNode(node, event, &was_handled),
|
||||
.xhr, .window, .abort_signal, .media_query_list, .message_port => {
|
||||
.xhr, .window, .abort_signal, .media_query_list, .message_port, .text_track_cue => {
|
||||
const list = self.lookup.getPtr(@intFromPtr(target)) orelse return;
|
||||
try self.dispatchAll(list, target, event, &was_handled);
|
||||
},
|
||||
|
||||
@@ -277,6 +277,15 @@ pub fn xhrEventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
).create(allocator, child);
|
||||
}
|
||||
|
||||
pub fn textTrackCue(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
const TextTrackCue = @import("webapi/media/TextTrackCue.zig");
|
||||
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, TextTrackCue, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
}
|
||||
|
||||
fn hasChainRoot(comptime T: type) bool {
|
||||
// Check if this is a root
|
||||
if (@hasDecl(T, "_prototype_root")) {
|
||||
|
||||
@@ -565,6 +565,8 @@ pub const JsApis = flattenTypes(&.{
|
||||
@import("../webapi/event/ProgressEvent.zig"),
|
||||
@import("../webapi/MessageChannel.zig"),
|
||||
@import("../webapi/MessagePort.zig"),
|
||||
@import("../webapi/media/TextTrackCue.zig"),
|
||||
@import("../webapi/media/VTTCue.zig"),
|
||||
@import("../webapi/EventTarget.zig"),
|
||||
@import("../webapi/Location.zig"),
|
||||
@import("../webapi/Navigator.zig"),
|
||||
|
||||
71
src/browser/tests/media/vttcue.html
Normal file
71
src/browser/tests/media/vttcue.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="..//testing.js"></script>
|
||||
|
||||
<script id=basic>
|
||||
{
|
||||
// Test that VTTCue constructor exists
|
||||
testing.expectEqual("function", typeof VTTCue);
|
||||
testing.expectEqual("function", typeof TextTrackCue);
|
||||
|
||||
// Create a basic VTTCue
|
||||
const cue = new VTTCue(0, 5, "Hello World");
|
||||
testing.expectEqual(0, cue.startTime);
|
||||
testing.expectEqual(5, cue.endTime);
|
||||
testing.expectEqual("Hello World", cue.text);
|
||||
}
|
||||
|
||||
{
|
||||
// Test property setters
|
||||
const cue = new VTTCue(1.5, 10.25, "Test text");
|
||||
cue.id = "test-cue";
|
||||
testing.expectEqual("test-cue", cue.id);
|
||||
|
||||
cue.startTime = 2.0;
|
||||
testing.expectEqual(2.0, cue.startTime);
|
||||
|
||||
cue.text = "Modified text";
|
||||
testing.expectEqual("Modified text", cue.text);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=defaults>
|
||||
{
|
||||
// Test default values
|
||||
const cue = new VTTCue(0, 1, "test");
|
||||
testing.expectEqual("", cue.vertical);
|
||||
testing.expectEqual(true, cue.snapToLines);
|
||||
testing.expectEqual("auto", cue.line);
|
||||
testing.expectEqual("auto", cue.position);
|
||||
testing.expectEqual(100, cue.size);
|
||||
testing.expectEqual("center", cue.align);
|
||||
testing.expectEqual(false, cue.pauseOnExit);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=properties>
|
||||
{
|
||||
// Test setting various properties
|
||||
const cue = new VTTCue(0, 5, "test");
|
||||
|
||||
cue.vertical = "rl";
|
||||
testing.expectEqual("rl", cue.vertical);
|
||||
|
||||
cue.snapToLines = false;
|
||||
testing.expectEqual(false, cue.snapToLines);
|
||||
|
||||
cue.line = 10;
|
||||
testing.expectEqual(10, cue.line);
|
||||
|
||||
cue.position = 50;
|
||||
testing.expectEqual(50, cue.position);
|
||||
|
||||
cue.size = 75;
|
||||
testing.expectEqual(75, cue.size);
|
||||
|
||||
cue.align = "left";
|
||||
testing.expectEqual("left", cue.align);
|
||||
|
||||
cue.pauseOnExit = true;
|
||||
testing.expectEqual(true, cue.pauseOnExit);
|
||||
}
|
||||
</script>
|
||||
@@ -36,6 +36,7 @@ pub const Type = union(enum) {
|
||||
abort_signal: *@import("AbortSignal.zig"),
|
||||
media_query_list: *@import("css/MediaQueryList.zig"),
|
||||
message_port: *@import("MessagePort.zig"),
|
||||
text_track_cue: *@import("media/TextTrackCue.zig"),
|
||||
};
|
||||
|
||||
pub fn dispatchEvent(self: *EventTarget, event: *Event, page: *Page) !bool {
|
||||
@@ -104,6 +105,7 @@ pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {
|
||||
.abort_signal => writer.writeAll("<abort_signal>"),
|
||||
.media_query_list => writer.writeAll("<MediaQueryList>"),
|
||||
.message_port => writer.writeAll("<MessagePort>"),
|
||||
.text_track_cue => writer.writeAll("<TextTrackCue>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
118
src/browser/webapi/media/TextTrackCue.zig
Normal file
118
src/browser/webapi/media/TextTrackCue.zig
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright (C) 2023-2025 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/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const EventTarget = @import("../EventTarget.zig");
|
||||
|
||||
const TextTrackCue = @This();
|
||||
|
||||
_type: Type,
|
||||
_proto: *EventTarget,
|
||||
_id: []const u8 = "",
|
||||
_start_time: f64 = 0,
|
||||
_end_time: f64 = 0,
|
||||
_pause_on_exit: bool = false,
|
||||
_on_enter: ?js.Function = null,
|
||||
_on_exit: ?js.Function = null,
|
||||
|
||||
pub const Type = union(enum) {
|
||||
vtt: *@import("VTTCue.zig"),
|
||||
};
|
||||
|
||||
pub fn asEventTarget(self: *TextTrackCue) *EventTarget {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getId(self: *const TextTrackCue) []const u8 {
|
||||
return self._id;
|
||||
}
|
||||
|
||||
pub fn setId(self: *TextTrackCue, value: []const u8, page: *Page) !void {
|
||||
self._id = try page.dupeString(value);
|
||||
}
|
||||
|
||||
pub fn getStartTime(self: *const TextTrackCue) f64 {
|
||||
return self._start_time;
|
||||
}
|
||||
|
||||
pub fn setStartTime(self: *TextTrackCue, value: f64) void {
|
||||
self._start_time = value;
|
||||
}
|
||||
|
||||
pub fn getEndTime(self: *const TextTrackCue) f64 {
|
||||
return self._end_time;
|
||||
}
|
||||
|
||||
pub fn setEndTime(self: *TextTrackCue, value: f64) void {
|
||||
self._end_time = value;
|
||||
}
|
||||
|
||||
pub fn getPauseOnExit(self: *const TextTrackCue) bool {
|
||||
return self._pause_on_exit;
|
||||
}
|
||||
|
||||
pub fn setPauseOnExit(self: *TextTrackCue, value: bool) void {
|
||||
self._pause_on_exit = value;
|
||||
}
|
||||
|
||||
pub fn getOnEnter(self: *const TextTrackCue) ?js.Function {
|
||||
return self._on_enter;
|
||||
}
|
||||
|
||||
pub fn setOnEnter(self: *TextTrackCue, cb_: ?js.Function) !void {
|
||||
if (cb_) |cb| {
|
||||
self._on_enter = try cb.withThis(self);
|
||||
} else {
|
||||
self._on_enter = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getOnExit(self: *const TextTrackCue) ?js.Function {
|
||||
return self._on_exit;
|
||||
}
|
||||
|
||||
pub fn setOnExit(self: *TextTrackCue, cb_: ?js.Function) !void {
|
||||
if (cb_) |cb| {
|
||||
self._on_exit = try cb.withThis(self);
|
||||
} else {
|
||||
self._on_exit = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(TextTrackCue);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "TextTrackCue";
|
||||
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const Prototype = EventTarget;
|
||||
|
||||
pub const id = bridge.accessor(TextTrackCue.getId, TextTrackCue.setId, .{});
|
||||
pub const startTime = bridge.accessor(TextTrackCue.getStartTime, TextTrackCue.setStartTime, .{});
|
||||
pub const endTime = bridge.accessor(TextTrackCue.getEndTime, TextTrackCue.setEndTime, .{});
|
||||
pub const pauseOnExit = bridge.accessor(TextTrackCue.getPauseOnExit, TextTrackCue.setPauseOnExit, .{});
|
||||
pub const onenter = bridge.accessor(TextTrackCue.getOnEnter, TextTrackCue.setOnEnter, .{});
|
||||
pub const onexit = bridge.accessor(TextTrackCue.getOnExit, TextTrackCue.setOnExit, .{});
|
||||
};
|
||||
182
src/browser/webapi/media/VTTCue.zig
Normal file
182
src/browser/webapi/media/VTTCue.zig
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright (C) 2023-2025 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/>.
|
||||
|
||||
const std = @import("std");
|
||||
const js = @import("../../js/js.zig");
|
||||
|
||||
const Page = @import("../../Page.zig");
|
||||
const TextTrackCue = @import("TextTrackCue.zig");
|
||||
|
||||
const VTTCue = @This();
|
||||
|
||||
_proto: *TextTrackCue,
|
||||
_text: []const u8 = "",
|
||||
_region: ?js.Object = null,
|
||||
_vertical: []const u8 = "",
|
||||
_snap_to_lines: bool = true,
|
||||
_line: ?f64 = null, // null represents "auto"
|
||||
_position: ?f64 = null, // null represents "auto"
|
||||
_size: f64 = 100,
|
||||
_align: []const u8 = "center",
|
||||
|
||||
pub fn constructor(start_time: f64, end_time: f64, text: []const u8, page: *Page) !*VTTCue {
|
||||
const cue = try page._factory.textTrackCue(VTTCue{
|
||||
._proto = undefined,
|
||||
._text = try page.dupeString(text),
|
||||
._region = null,
|
||||
._vertical = "",
|
||||
._snap_to_lines = true,
|
||||
._line = null, // "auto"
|
||||
._position = null, // "auto"
|
||||
._size = 100,
|
||||
._align = "center",
|
||||
});
|
||||
|
||||
cue._proto._start_time = start_time;
|
||||
cue._proto._end_time = end_time;
|
||||
|
||||
return cue;
|
||||
}
|
||||
|
||||
pub fn asTextTrackCue(self: *VTTCue) *TextTrackCue {
|
||||
return self._proto;
|
||||
}
|
||||
|
||||
pub fn getText(self: *const VTTCue) []const u8 {
|
||||
return self._text;
|
||||
}
|
||||
|
||||
pub fn setText(self: *VTTCue, value: []const u8, page: *Page) !void {
|
||||
self._text = try page.dupeString(value);
|
||||
}
|
||||
|
||||
pub fn getRegion(self: *const VTTCue) ?js.Object {
|
||||
return self._region;
|
||||
}
|
||||
|
||||
pub fn setRegion(self: *VTTCue, value: ?js.Object) !void {
|
||||
if (value) |v| {
|
||||
self._region = try v.persist();
|
||||
} else {
|
||||
self._region = null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getVertical(self: *const VTTCue) []const u8 {
|
||||
return self._vertical;
|
||||
}
|
||||
|
||||
pub fn setVertical(self: *VTTCue, value: []const u8, page: *Page) !void {
|
||||
// Valid values: "", "rl", "lr"
|
||||
self._vertical = try page.dupeString(value);
|
||||
}
|
||||
|
||||
pub fn getSnapToLines(self: *const VTTCue) bool {
|
||||
return self._snap_to_lines;
|
||||
}
|
||||
|
||||
pub fn setSnapToLines(self: *VTTCue, value: bool) void {
|
||||
self._snap_to_lines = value;
|
||||
}
|
||||
|
||||
pub const LineAndPositionSetting = union(enum) {
|
||||
number: f64,
|
||||
auto: []const u8,
|
||||
};
|
||||
|
||||
pub fn getLine(self: *const VTTCue) LineAndPositionSetting {
|
||||
if (self._line) |num| {
|
||||
return .{ .number = num };
|
||||
}
|
||||
return .{ .auto = "auto" };
|
||||
}
|
||||
|
||||
pub fn setLine(self: *VTTCue, value: LineAndPositionSetting) void {
|
||||
switch (value) {
|
||||
.number => |num| self._line = num,
|
||||
.auto => self._line = null,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getPosition(self: *const VTTCue) LineAndPositionSetting {
|
||||
if (self._position) |num| {
|
||||
return .{ .number = num };
|
||||
}
|
||||
return .{ .auto = "auto" };
|
||||
}
|
||||
|
||||
pub fn setPosition(self: *VTTCue, value: LineAndPositionSetting) void {
|
||||
switch (value) {
|
||||
.number => |num| self._position = num,
|
||||
.auto => self._position = null,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getSize(self: *const VTTCue) f64 {
|
||||
return self._size;
|
||||
}
|
||||
|
||||
pub fn setSize(self: *VTTCue, value: f64) void {
|
||||
self._size = value;
|
||||
}
|
||||
|
||||
pub fn getAlign(self: *const VTTCue) []const u8 {
|
||||
return self._align;
|
||||
}
|
||||
|
||||
pub fn setAlign(self: *VTTCue, value: []const u8, page: *Page) !void {
|
||||
// Valid values: "start", "center", "end", "left", "right"
|
||||
self._align = try page.dupeString(value);
|
||||
}
|
||||
|
||||
pub fn getCueAsHTML(self: *const VTTCue, page: *Page) !js.Object {
|
||||
// Minimal implementation: return a document fragment
|
||||
// In a full implementation, this would parse the VTT text into HTML nodes
|
||||
_ = self;
|
||||
_ = page;
|
||||
return error.NotImplemented;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
pub const bridge = js.Bridge(VTTCue);
|
||||
|
||||
pub const Meta = struct {
|
||||
pub const name = "VTTCue";
|
||||
|
||||
pub const prototype_chain = bridge.prototypeChain();
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const Prototype = TextTrackCue;
|
||||
|
||||
pub const constructor = bridge.constructor(VTTCue.constructor, .{});
|
||||
pub const text = bridge.accessor(VTTCue.getText, VTTCue.setText, .{});
|
||||
pub const region = bridge.accessor(VTTCue.getRegion, VTTCue.setRegion, .{});
|
||||
pub const vertical = bridge.accessor(VTTCue.getVertical, VTTCue.setVertical, .{});
|
||||
pub const snapToLines = bridge.accessor(VTTCue.getSnapToLines, VTTCue.setSnapToLines, .{});
|
||||
pub const line = bridge.accessor(VTTCue.getLine, VTTCue.setLine, .{});
|
||||
pub const position = bridge.accessor(VTTCue.getPosition, VTTCue.setPosition, .{});
|
||||
pub const size = bridge.accessor(VTTCue.getSize, VTTCue.setSize, .{});
|
||||
pub const @"align" = bridge.accessor(VTTCue.getAlign, VTTCue.setAlign, .{});
|
||||
pub const getCueAsHTML = bridge.function(VTTCue.getCueAsHTML, .{});
|
||||
};
|
||||
|
||||
const testing = @import("../../../testing.zig");
|
||||
test "WebApi: VTTCue" {
|
||||
try testing.htmlRunner("media/vttcue.html", .{});
|
||||
}
|
||||
Reference in New Issue
Block a user