From 69bac645e0855bed2e4c033924003e01419ba794 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 14 Mar 2024 08:37:01 -0700 Subject: [PATCH] Add `v8::MicrotaskQueue` bindings (#1423) --- src/binding.cc | 28 +++++++++++++++++ src/context.rs | 20 ++++++++++++ src/lib.rs | 2 ++ src/microtask.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test_api.rs | 29 ++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 src/microtask.rs diff --git a/src/binding.cc b/src/binding.cc index 7d3238b0..a7365473 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -1882,6 +1882,15 @@ bool v8__Context_IsCodeGenerationFromStringsAllowed(v8::Context& self) { return ptr_to_local(&self)->IsCodeGenerationFromStringsAllowed(); } +v8::MicrotaskQueue* v8__Context__GetMicrotaskQueue(v8::Context& self) { + return ptr_to_local(&self)->GetMicrotaskQueue(); +} + +void v8__Context__SetMicrotaskQueue(v8::Context& self, + v8::MicrotaskQueue* microtask_queue) { + ptr_to_local(&self)->SetMicrotaskQueue(microtask_queue); +} + const v8::Context* v8__Context__FromSnapshot(v8::Isolate* isolate, size_t context_snapshot_index) { v8::MaybeLocal maybe_local = @@ -1900,6 +1909,25 @@ const v8::Value* v8__Context__GetContinuationPreservedEmbedderData( return local_to_ptr(value); } +void v8__MicrotaskQueue__PerformCheckpoint(v8::Isolate* isolate, + v8::MicrotaskQueue* self) { + self->PerformCheckpoint(isolate); +} + +bool v8__MicrotaskQueue__IsRunningMicrotasks(v8::MicrotaskQueue* self) { + return self->IsRunningMicrotasks(); +} + +int v8__MicrotaskQueue__GetMicrotasksScopeDepth(v8::MicrotaskQueue* self) { + return self->GetMicrotasksScopeDepth(); +} + +void v8__MicrotaskQueue__EnqueueMicrotask(v8::Isolate* isolate, + v8::MicrotaskQueue* self, + v8::Function* callback) { + self->EnqueueMicrotask(isolate, ptr_to_local(callback)); +} + const v8::String* v8__Message__Get(const v8::Message& self) { return local_to_ptr(self.Get()); } diff --git a/src/context.rs b/src/context.rs index cbae84b7..d202804b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -7,6 +7,7 @@ use crate::support::int; use crate::Context; use crate::HandleScope; use crate::Local; +use crate::MicrotaskQueue; use crate::Object; use crate::ObjectTemplate; use crate::Value; @@ -55,6 +56,13 @@ extern "C" { pub(super) fn v8__Context_IsCodeGenerationFromStringsAllowed( this: *const Context, ) -> bool; + fn v8__Context__GetMicrotaskQueue( + this: *const Context, + ) -> *const MicrotaskQueue; + fn v8__Context__SetMicrotaskQueue( + this: *const Context, + microtask_queue: *const MicrotaskQueue, + ); } impl Context { @@ -114,6 +122,18 @@ impl Context { unsafe { scope.cast_local(|_| v8__Context__Global(self)) }.unwrap() } + #[inline(always)] + pub fn get_microtask_queue(&self) -> &MicrotaskQueue { + unsafe { &*v8__Context__GetMicrotaskQueue(self) } + } + + #[inline(always)] + pub fn set_microtask_queue(&self, microtask_queue: &MicrotaskQueue) { + unsafe { + v8__Context__SetMicrotaskQueue(self, microtask_queue); + } + } + #[inline] fn get_annex_mut<'a>( &'a self, diff --git a/src/lib.rs b/src/lib.rs index 3e04c6f8..c513276f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,6 +46,7 @@ mod handle; pub mod icu; mod isolate; mod isolate_create_params; +mod microtask; mod module; mod name; mod number; @@ -117,6 +118,7 @@ pub use isolate::PromiseHookType; pub use isolate::PromiseRejectCallback; pub use isolate::WasmAsyncSuccess; pub use isolate_create_params::CreateParams; +pub use microtask::MicrotaskQueue; pub use module::*; pub use object::*; pub use platform::new_default_platform; diff --git a/src/microtask.rs b/src/microtask.rs new file mode 100644 index 00000000..fb936c78 --- /dev/null +++ b/src/microtask.rs @@ -0,0 +1,78 @@ +// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. + +use crate::support::int; +use crate::support::Opaque; +use crate::Function; +use crate::Isolate; +use crate::Local; + +extern "C" { + fn v8__MicrotaskQueue__PerformCheckpoint( + isolate: *mut Isolate, + queue: *const MicrotaskQueue, + ); + fn v8__MicrotaskQueue__IsRunningMicrotasks( + queue: *const MicrotaskQueue, + ) -> bool; + fn v8__MicrotaskQueue__GetMicrotasksScopeDepth( + queue: *const MicrotaskQueue, + ) -> int; + fn v8__MicrotaskQueue__EnqueueMicrotask( + isolate: *mut Isolate, + queue: *const MicrotaskQueue, + microtask: *const Function, + ); +} + +/// Represents the microtask queue, where microtasks are stored and processed. +/// https://html.spec.whatwg.org/multipage/webappapis.html#microtask-queue +/// https://html.spec.whatwg.org/multipage/webappapis.html#enqueuejob(queuename,-job,-arguments) +/// https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint +/// +/// A MicrotaskQueue instance may be associated to multiple Contexts by passing +/// it to Context::New(), and they can be detached by Context::DetachGlobal(). +/// The embedder must keep the MicrotaskQueue instance alive until all associated +/// Contexts are gone or detached. +/// +/// Use the same instance of MicrotaskQueue for all Contexts that may access each +/// other synchronously. E.g. for Web embedding, use the same instance for all +/// origins that share the same URL scheme and eTLD+1. +#[repr(C)] +#[derive(Debug)] +pub struct MicrotaskQueue(Opaque); + +impl MicrotaskQueue { + pub fn enqueue_microtask( + &self, + isolate: &mut Isolate, + microtask: Local, + ) { + unsafe { v8__MicrotaskQueue__EnqueueMicrotask(isolate, self, &*microtask) } + } + + /// Adds a callback to notify the embedder after microtasks were run. The + /// callback is triggered by explicit RunMicrotasks call or automatic + /// microtasks execution (see Isolate::SetMicrotasksPolicy). + /// + /// Callback will trigger even if microtasks were attempted to run, + /// but the microtasks queue was empty and no single microtask was actually + /// executed. + /// + /// Executing scripts inside the callback will not re-trigger microtasks and + /// the callback. + pub fn perform_checkpoint(&self, isolate: &mut Isolate) { + unsafe { + v8__MicrotaskQueue__PerformCheckpoint(isolate, self); + } + } + + /// Removes callback that was installed by AddMicrotasksCompletedCallback. + pub fn is_running_microtasks(&self) -> bool { + unsafe { v8__MicrotaskQueue__IsRunningMicrotasks(self) } + } + + /// Returns the current depth of nested MicrotasksScope that has kRunMicrotasks. + pub fn get_microtasks_scope_depth(&self) -> i32 { + unsafe { v8__MicrotaskQueue__GetMicrotasksScopeDepth(self) } + } +} diff --git a/tests/test_api.rs b/tests/test_api.rs index 33ef993a..df0d2b68 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -11324,3 +11324,32 @@ fn allow_scope_in_read_host_object() { let value = deserializer.read_value(context).unwrap(); assert!(value.is_object()); } + +#[test] +fn microtask_queue() { + let _setup_guard = setup::parallel_test(); + let mut isolate = v8::Isolate::new(Default::default()); + + let mut scope = v8::HandleScope::new(&mut isolate); + let context = v8::Context::new(&mut scope); + + let queue = context.get_microtask_queue(); + let mut scope = v8::ContextScope::new(&mut scope, context); + + static CALL_COUNT: AtomicUsize = AtomicUsize::new(0); + let function = v8::Function::new( + &mut scope, + |_: &mut v8::HandleScope, + _: v8::FunctionCallbackArguments, + _: v8::ReturnValue| { + CALL_COUNT.fetch_add(1, Ordering::SeqCst); + }, + ) + .unwrap(); + + queue.enqueue_microtask(&mut scope, function); + // Flushes the microtasks queue. + let _ = eval(&mut scope, "").unwrap(); + + assert_eq!(CALL_COUNT.load(Ordering::SeqCst), 1); +}