Skip to content

Commit f8ca45f

Browse files
committed
Add Element.animate and Animation
These are dummy implementations, but they do expose the ready and finished promise, and do resolve the finished promise, so it should unblock basic cases.
1 parent 3bc654b commit f8ca45f

File tree

5 files changed

+177
-0
lines changed

5 files changed

+177
-0
lines changed

src/browser/dom/Animation.zig

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright (C) 2023-2025s Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
21+
const Page = @import("../page.zig").Page;
22+
const JsObject = @import("../env.zig").JsObject;
23+
const Promise = @import("../env.zig").Promise;
24+
const PromiseResolver = @import("../env.zig").PromiseResolver;
25+
26+
const Animation = @This();
27+
28+
effect: ?JsObject,
29+
timeline: ?JsObject,
30+
ready_resolver: ?PromiseResolver,
31+
finished_resolver: ?PromiseResolver,
32+
33+
pub fn constructor(effect: ?JsObject, timeline: ?JsObject) !Animation {
34+
return .{
35+
.effect = if (effect) |eo| try eo.persist() else null,
36+
.timeline = if (timeline) |to| try to.persist() else null,
37+
.ready_resolver = null,
38+
.finished_resolver = null,
39+
};
40+
}
41+
42+
pub fn get_playState(self: *const Animation) []const u8 {
43+
_ = self;
44+
return "finished";
45+
}
46+
47+
pub fn get_pending(self: *const Animation) bool {
48+
_ = self;
49+
return false;
50+
}
51+
52+
pub fn get_finished(self: *Animation, page: *Page) !Promise {
53+
if (self.finished_resolver == null) {
54+
const resolver = page.main_context.createPromiseResolver();
55+
try resolver.resolve(self);
56+
self.finished_resolver = resolver;
57+
}
58+
return self.finished_resolver.?.promise();
59+
}
60+
61+
pub fn get_ready(self: *Animation, page: *Page) !Promise {
62+
// never resolved, because we're always "finished"
63+
if (self.ready_resolver == null) {
64+
const resolver = page.main_context.createPromiseResolver();
65+
self.ready_resolver = resolver;
66+
}
67+
return self.ready_resolver.?.promise();
68+
}
69+
70+
pub fn get_effect(self: *const Animation) ?JsObject {
71+
return self.effect;
72+
}
73+
74+
pub fn set_effect(self: *Animation, effect: JsObject) !void {
75+
self.effect = try effect.persist();
76+
}
77+
78+
pub fn get_timeline(self: *const Animation) ?JsObject {
79+
return self.timeline;
80+
}
81+
82+
pub fn set_timeline(self: *Animation, timeline: JsObject) !void {
83+
self.timeline = try timeline.persist();
84+
}
85+
86+
pub fn _play(self: *const Animation) void {
87+
_ = self;
88+
}
89+
90+
pub fn _pause(self: *const Animation) void {
91+
_ = self;
92+
}
93+
94+
pub fn _cancel(self: *const Animation) void {
95+
_ = self;
96+
}
97+
98+
pub fn _finish(self: *const Animation) void {
99+
_ = self;
100+
}
101+
102+
pub fn _reverse(self: *const Animation) void {
103+
_ = self;
104+
}
105+
106+
const testing = @import("../../testing.zig");
107+
test "Browser.Animation" {
108+
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .html = "" });
109+
defer runner.deinit();
110+
111+
try runner.testCases(&.{
112+
.{ "let a1 = document.createElement('div').animate(null, null)", null },
113+
.{ "a1.playState", "finished" },
114+
.{ "let cb = [];", null },
115+
.{ "a1.ready.then(() => { cb.push('ready') })", null },
116+
.{
117+
\\ a1.finished.then((x) => {
118+
\\ cb.push('finished');
119+
\\ cb.push(x == a1);
120+
\\ })
121+
,
122+
null,
123+
},
124+
.{ "cb", "finished,true" },
125+
}, .{});
126+
}

src/browser/dom/dom.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,5 @@ pub const Interfaces = .{
5252
@import("performance.zig").Interfaces,
5353
PerformanceObserver,
5454
@import("range.zig").Interfaces,
55+
@import("Animation.zig"),
5556
};

src/browser/dom/element.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ const NodeList = @import("nodelist.zig").NodeList;
3232
const HTMLElem = @import("../html/elements.zig");
3333
const ShadowRoot = @import("../dom/shadow_root.zig").ShadowRoot;
3434

35+
const Animation = @import("Animation.zig");
36+
const JsObject = @import("../env.zig").JsObject;
37+
3538
pub const Union = @import("../html/elements.zig").Union;
3639

3740
// WEB IDL https://dom.spec.whatwg.org/#element
@@ -499,6 +502,12 @@ pub const Element = struct {
499502
}
500503
return sr;
501504
}
505+
506+
pub fn _animate(self: *parser.Element, effect: JsObject, opts: JsObject) !Animation {
507+
_ = self;
508+
_ = opts;
509+
return Animation.constructor(effect, null);
510+
}
502511
};
503512

504513
// Tests

src/browser/env.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,8 @@ const WebApis = struct {
4141
pub const JsThis = Env.JsThis;
4242
pub const JsObject = Env.JsObject;
4343
pub const Function = Env.Function;
44+
pub const Promise = Env.Promise;
45+
pub const PromiseResolver = Env.PromiseResolver;
46+
4447
pub const Env = js.Env(*Page, WebApis);
4548
pub const Global = @import("html/window.zig").Window;

src/runtime/js.zig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,13 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
11641164
};
11651165
}
11661166

1167+
pub fn createPromiseResolver(self: *JsContext) PromiseResolver {
1168+
return .{
1169+
.js_context = self,
1170+
.resolver = v8.PromiseResolver.init(self.v8_context),
1171+
};
1172+
}
1173+
11671174
// Probing is part of trying to map a JS value to a Zig union. There's
11681175
// a lot of ambiguity in this process, in part because some JS values
11691176
// can almost always be coerced. For example, anything can be coerced
@@ -1871,6 +1878,32 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
18711878
};
18721879
}
18731880

1881+
pub const PromiseResolver = struct {
1882+
js_context: *JsContext,
1883+
resolver: v8.PromiseResolver,
1884+
1885+
pub fn promise(self: PromiseResolver) Promise {
1886+
return .{
1887+
.promise = self.resolver.getPromise(),
1888+
};
1889+
}
1890+
1891+
pub fn resolve(self: PromiseResolver, value: anytype) !void {
1892+
const js_context = self.js_context;
1893+
const js_value = try js_context.zigValueToJs(value);
1894+
1895+
// resolver.resolve will return null if the promise isn't pending
1896+
const ok = self.resolver.resolve(js_context.v8_context, js_value) orelse return;
1897+
if (!ok) {
1898+
return error.FailedToResolvePromise;
1899+
}
1900+
}
1901+
};
1902+
1903+
pub const Promise = struct {
1904+
promise: v8.Promise,
1905+
};
1906+
18741907
pub const Inspector = struct {
18751908
isolate: v8.Isolate,
18761909
inner: *v8.Inspector,
@@ -2475,6 +2508,11 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
24752508
return value.js_obj.toValue();
24762509
}
24772510

2511+
if (T == Promise) {
2512+
// we're returning a v8.Promise
2513+
return value.promise.toObject().toValue();
2514+
}
2515+
24782516
if (@hasDecl(T, "_EXCEPTION_ID_KLUDGE")) {
24792517
return isolate.throwException(value.inner);
24802518
}

0 commit comments

Comments
 (0)