@@ -47,6 +47,11 @@ unsafe extern "C" {
4747 isolate : * mut RealIsolate ,
4848 ) ;
4949
50+ fn v8__Task__Run ( task : * mut std:: ffi:: c_void ) ;
51+ fn v8__Task__DELETE ( task : * mut std:: ffi:: c_void ) ;
52+ fn v8__IdleTask__Run ( task : * mut std:: ffi:: c_void , deadline_in_seconds : f64 ) ;
53+ fn v8__IdleTask__DELETE ( task : * mut std:: ffi:: c_void ) ;
54+
5055 fn std__shared_ptr__v8__Platform__CONVERT__std__unique_ptr (
5156 unique_ptr : UniquePtr < Platform > ,
5257 ) -> SharedPtrBase < Platform > ;
@@ -66,52 +71,141 @@ unsafe extern "C" {
6671#[ derive( Debug ) ]
6772pub struct Platform ( Opaque ) ;
6873
74+ /// A V8 foreground task. Ownership is transferred from C++ to Rust when
75+ /// V8 posts a task via [`PlatformImpl`] trait methods.
76+ ///
77+ /// The embedder is responsible for scheduling the task and calling
78+ /// [`run()`](Task::run). For example, in an async runtime like tokio:
79+ ///
80+ /// ```ignore
81+ /// tokio::spawn(async move { task.run() });
82+ /// ```
83+ ///
84+ /// If dropped without calling `run()`, the task is destroyed without
85+ /// executing.
86+ pub struct Task ( * mut std:: ffi:: c_void ) ;
87+
88+ // SAFETY: V8 tasks are designed to be posted from background threads and
89+ // run on the isolate's foreground thread. The unique_ptr transfer is safe
90+ // across thread boundaries.
91+ unsafe impl Send for Task { }
92+
93+ impl Task {
94+ /// Run the task. Consumes self to prevent double execution.
95+ pub fn run ( self ) {
96+ let ptr = self . 0 ;
97+ // Prevent Drop from deleting — we'll delete after Run.
98+ std:: mem:: forget ( self ) ;
99+ unsafe {
100+ v8__Task__Run ( ptr) ;
101+ v8__Task__DELETE ( ptr) ;
102+ }
103+ }
104+ }
105+
106+ impl Drop for Task {
107+ fn drop ( & mut self ) {
108+ unsafe { v8__Task__DELETE ( self . 0 ) } ;
109+ }
110+ }
111+
112+ /// A V8 idle task. Similar to [`Task`] but accepts a deadline parameter
113+ /// when run.
114+ ///
115+ /// If dropped without calling `run()`, the task is destroyed without
116+ /// executing.
117+ pub struct IdleTask ( * mut std:: ffi:: c_void ) ;
118+
119+ // SAFETY: Same as Task — safe to transfer across threads.
120+ unsafe impl Send for IdleTask { }
121+
122+ impl IdleTask {
123+ /// Run the idle task with the given deadline. Consumes self.
124+ ///
125+ /// `deadline_in_seconds` is the absolute time (in seconds since some
126+ /// epoch) by which the idle task should complete.
127+ pub fn run ( self , deadline_in_seconds : f64 ) {
128+ let ptr = self . 0 ;
129+ std:: mem:: forget ( self ) ;
130+ unsafe {
131+ v8__IdleTask__Run ( ptr, deadline_in_seconds) ;
132+ v8__IdleTask__DELETE ( ptr) ;
133+ }
134+ }
135+ }
136+
137+ impl Drop for IdleTask {
138+ fn drop ( & mut self ) {
139+ unsafe { v8__IdleTask__DELETE ( self . 0 ) } ;
140+ }
141+ }
142+
69143/// Trait for customizing platform behavior, following the same pattern as
70144/// [`V8InspectorClientImpl`](crate::inspector::V8InspectorClientImpl).
71145///
72- /// Implement this trait to receive callbacks for overridden C++ virtual
73- /// methods on the `DefaultPlatform` and its per-isolate `TaskRunner`.
146+ /// Implement this trait to receive V8 foreground tasks and schedule them
147+ /// on your event loop. The C++ `CustomPlatform` wraps each isolate's
148+ /// `TaskRunner` so that every `PostTask` / `PostDelayedTask` / etc. call
149+ /// transfers task ownership to Rust through the corresponding trait method.
150+ ///
151+ /// **The embedder is responsible for calling [`Task::run()`] on the
152+ /// isolate's thread.** For example, using tokio:
153+ ///
154+ /// ```ignore
155+ /// fn post_task(&self, isolate_ptr: *mut c_void, task: Task) {
156+ /// tokio::spawn(async move { task.run() });
157+ /// }
74158///
75- /// The C++ `CustomPlatform` wraps each isolate's `TaskRunner` so that
76- /// every `PostTask` / `PostDelayedTask` / etc. call is forwarded to the
77- /// default implementation *and* notifies Rust through the corresponding
78- /// trait method.
159+ /// fn post_delayed_task(&self, isolate_ptr: *mut c_void, task: Task, delay: f64) {
160+ /// tokio::spawn(async move {
161+ /// tokio::time::sleep(Duration::from_secs_f64(delay)).await;
162+ /// task.run();
163+ /// });
164+ /// }
165+ /// ```
79166///
80- /// All methods have default no-op implementations; override only what
81- /// you need .
167+ /// All methods have default implementations that run the task immediately
168+ /// (synchronously). Override to integrate with your event loop .
82169///
83170/// Implementations must be `Send + Sync` as callbacks may fire from any
84171/// thread.
85172#[ allow( unused_variables) ]
86173pub trait PlatformImpl : Send + Sync {
87- // ---- TaskRunner virtual methods ----
88-
89174 /// Called when `TaskRunner::PostTask` is invoked for the given isolate.
90175 ///
91- /// The task itself has already been forwarded to the default platform's
92- /// queue and will be executed by `PumpMessageLoop`. This callback is a
93- /// notification that a new task is available.
176+ /// The [`Task`] must be run on the isolate's foreground thread by calling
177+ /// [`Task::run()`].
94178 ///
95179 /// May be called from ANY thread (V8 background threads, etc.).
96- fn post_task ( & self , isolate_ptr : * mut std:: ffi:: c_void ) { }
180+ fn post_task ( & self , isolate_ptr : * mut std:: ffi:: c_void , task : Task ) {
181+ task. run ( ) ;
182+ }
97183
98184 /// Called when `TaskRunner::PostNonNestableTask` is invoked.
99185 ///
100- /// Same semantics as [`post_task`](Self::post_task).
101- fn post_non_nestable_task ( & self , isolate_ptr : * mut std:: ffi:: c_void ) { }
186+ /// Same semantics as [`post_task`](Self::post_task), but the task must
187+ /// not be run within a nested `PumpMessageLoop`.
188+ fn post_non_nestable_task (
189+ & self ,
190+ isolate_ptr : * mut std:: ffi:: c_void ,
191+ task : Task ,
192+ ) {
193+ task. run ( ) ;
194+ }
102195
103196 /// Called when `TaskRunner::PostDelayedTask` is invoked.
104197 ///
105- /// The task has been forwarded to the default runner's delayed queue.
106- /// `delay_in_seconds` is the delay before the task should execute.
107- /// Embedders should schedule a wake-up after this delay.
198+ /// The task should be run after `delay_in_seconds` has elapsed.
199+ /// For example, using `tokio::time::sleep` or a timer wheel.
108200 ///
109201 /// May be called from ANY thread.
110202 fn post_delayed_task (
111203 & self ,
112204 isolate_ptr : * mut std:: ffi:: c_void ,
205+ task : Task ,
113206 delay_in_seconds : f64 ,
114207 ) {
208+ task. run ( ) ;
115209 }
116210
117211 /// Called when `TaskRunner::PostNonNestableDelayedTask` is invoked.
@@ -120,64 +214,75 @@ pub trait PlatformImpl: Send + Sync {
120214 fn post_non_nestable_delayed_task (
121215 & self ,
122216 isolate_ptr : * mut std:: ffi:: c_void ,
217+ task : Task ,
123218 delay_in_seconds : f64 ,
124219 ) {
220+ task. run ( ) ;
125221 }
126222
127223 /// Called when `TaskRunner::PostIdleTask` is invoked.
128224 ///
129- /// Same semantics as [`post_task`](Self::post_task).
130- fn post_idle_task ( & self , isolate_ptr : * mut std:: ffi:: c_void ) { }
225+ /// The [`IdleTask`] should be run when the embedder has idle time,
226+ /// passing the deadline via [`IdleTask::run(deadline)`](IdleTask::run).
227+ fn post_idle_task ( & self , isolate_ptr : * mut std:: ffi:: c_void , task : IdleTask ) {
228+ task. run ( 0.0 ) ;
229+ }
131230}
132231
133232// FFI callbacks called from C++ CustomPlatform/CustomTaskRunner.
134233// `context` is a raw pointer to a `Box<dyn PlatformImpl>`.
234+ // Task pointers are owned — Rust is responsible for running and deleting them.
135235
136236#[ unsafe( no_mangle) ]
137237unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostTask (
138238 context : * mut std:: ffi:: c_void ,
139239 isolate : * mut std:: ffi:: c_void ,
240+ task : * mut std:: ffi:: c_void ,
140241) {
141242 let imp = unsafe { & * ( context as * const Box < dyn PlatformImpl > ) } ;
142- imp. post_task ( isolate) ;
243+ imp. post_task ( isolate, Task ( task ) ) ;
143244}
144245
145246#[ unsafe( no_mangle) ]
146247unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableTask (
147248 context : * mut std:: ffi:: c_void ,
148249 isolate : * mut std:: ffi:: c_void ,
250+ task : * mut std:: ffi:: c_void ,
149251) {
150252 let imp = unsafe { & * ( context as * const Box < dyn PlatformImpl > ) } ;
151- imp. post_non_nestable_task ( isolate) ;
253+ imp. post_non_nestable_task ( isolate, Task ( task ) ) ;
152254}
153255
154256#[ unsafe( no_mangle) ]
155257unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostDelayedTask (
156258 context : * mut std:: ffi:: c_void ,
157259 isolate : * mut std:: ffi:: c_void ,
260+ task : * mut std:: ffi:: c_void ,
158261 delay_in_seconds : f64 ,
159262) {
160263 let imp = unsafe { & * ( context as * const Box < dyn PlatformImpl > ) } ;
161- imp. post_delayed_task ( isolate, delay_in_seconds) ;
264+ imp. post_delayed_task ( isolate, Task ( task ) , delay_in_seconds) ;
162265}
163266
164267#[ unsafe( no_mangle) ]
165268unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostNonNestableDelayedTask (
166269 context : * mut std:: ffi:: c_void ,
167270 isolate : * mut std:: ffi:: c_void ,
271+ task : * mut std:: ffi:: c_void ,
168272 delay_in_seconds : f64 ,
169273) {
170274 let imp = unsafe { & * ( context as * const Box < dyn PlatformImpl > ) } ;
171- imp. post_non_nestable_delayed_task ( isolate, delay_in_seconds) ;
275+ imp. post_non_nestable_delayed_task ( isolate, Task ( task ) , delay_in_seconds) ;
172276}
173277
174278#[ unsafe( no_mangle) ]
175279unsafe extern "C" fn v8__Platform__CustomPlatform__BASE__PostIdleTask (
176280 context : * mut std:: ffi:: c_void ,
177281 isolate : * mut std:: ffi:: c_void ,
282+ task : * mut std:: ffi:: c_void ,
178283) {
179284 let imp = unsafe { & * ( context as * const Box < dyn PlatformImpl > ) } ;
180- imp. post_idle_task ( isolate) ;
285+ imp. post_idle_task ( isolate, IdleTask ( task ) ) ;
181286}
182287
183288#[ unsafe( no_mangle) ]
@@ -240,11 +345,15 @@ pub fn new_single_threaded_default_platform(
240345 Platform :: new_single_threaded ( idle_task_support)
241346}
242347
243- /// Creates a custom platform backed by `DefaultPlatform` that delegates
244- /// virtual method overrides to the provided [`PlatformImpl`] trait object.
348+ /// Creates a custom platform backed by `DefaultPlatform` that transfers
349+ /// foreground task ownership to the provided [`PlatformImpl`] trait object.
245350///
246- /// This follows the same pattern as
247- /// [`V8InspectorClient::new`](crate::inspector::V8InspectorClient::new).
351+ /// Unlike the default platform, foreground tasks are NOT queued internally.
352+ /// Instead, each `PostTask` / `PostDelayedTask` / etc. call transfers the
353+ /// [`Task`] to Rust via the trait. The embedder is responsible for
354+ /// scheduling and calling [`Task::run()`] on the isolate's thread.
355+ ///
356+ /// Background tasks (thread pool) are still handled by `DefaultPlatform`.
248357///
249358/// When `unprotected` is true, thread-isolated allocations are disabled
250359/// (same as `new_unprotected_default_platform`). This is required when
@@ -330,8 +439,10 @@ impl Platform {
330439 }
331440 }
332441
333- /// Creates a custom platform backed by `DefaultPlatform` that delegates
334- /// virtual method overrides to the provided [`PlatformImpl`] trait object.
442+ /// Creates a custom platform that transfers foreground task ownership to
443+ /// the provided [`PlatformImpl`] trait object.
444+ ///
445+ /// See [`new_custom_platform`] for details.
335446 ///
336447 /// The trait object is owned by the platform and will be dropped when the
337448 /// platform is destroyed.
0 commit comments