From 9da4c74a60f24b1b2b54e6e62548d83911869925 Mon Sep 17 00:00:00 2001 From: shuppy Date: Mon, 28 Jul 2025 19:39:35 +0800 Subject: [PATCH] script: Implement jsglue traps for saveJobQueue() (#38232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit in the [SpiderMonkey Debugger API](https://firefox-source-docs.mozilla.org/js/Debugger/), hooks like [onNewGlobalObject()](https://firefox-source-docs.mozilla.org/js/Debugger/Debugger.html#onnewglobalobject-global) use an AutoDebuggerJobQueueInterruption to [switch to a new microtask queue](https://github.com/servo/mozjs/blob/b14aebff23ac4d5b0652060ef949334bda08b22f/mozjs-sys/mozjs/js/src/debugger/Debugger.cpp#L2834-L2841) and avoid clobbering the debuggee’s microtask queue. this in turn relies on JobQueue::saveJobQueue(), which is [not yet implemented in RustJobQueue](https://github.com/servo/mozjs/blob/b14aebff23ac4d5b0652060ef949334bda08b22f/mozjs-sys/src/jsglue.cpp#L83-L86). this patch bumps mozjs to servo/mozjs#595, which implements [saveJobQueue() and SavedJobQueue](https://github.com/servo/mozjs/blob/b14aebff23ac4d5b0652060ef949334bda08b22f/mozjs-sys/mozjs/js/public/Promise.h#L92-L114) for RustJobQueue by calling into Servo via two new JobQueueTraps that create and destroy extra “interrupt” queues for use by the debugger. SpiderMonkey [does not own external job queues](https://github.com/servo/mozjs/blob/b14aebff23ac4d5b0652060ef949334bda08b22f/mozjs-sys/mozjs/js/public/Promise.h#L117-L123), so the lifetime of these queues is managed in Servo, where they are stored in a Vec-based stack. stack-like behaviour is adequate for SpiderMonkey’s save and restore patterns, as far as we can tell, but we’ve added an assertion just in case. Testing: manually tested working in devtools debugger patch (#37667), where it will undergo automated tests Fixes: #38311 --------- Signed-off-by: Delan Azabani Co-authored-by: atbrakhi --- Cargo.lock | 6 ++-- components/script/script_runtime.rs | 48 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c6c718a122..58d5c0b5afd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5240,7 +5240,7 @@ dependencies = [ [[package]] name = "mozjs" version = "0.14.1" -source = "git+https://github.com/servo/mozjs#9c017973a4bf9186df2335d8c96ed3554b84434e" +source = "git+https://github.com/servo/mozjs#b23161580b082e1ccfa3273d94f43f6168aedc3d" dependencies = [ "bindgen 0.71.1", "cc", @@ -5251,8 +5251,8 @@ dependencies = [ [[package]] name = "mozjs_sys" -version = "0.128.13-1" -source = "git+https://github.com/servo/mozjs#9c017973a4bf9186df2335d8c96ed3554b84434e" +version = "0.128.13-2" +source = "git+https://github.com/servo/mozjs#b23161580b082e1ccfa3273d94f43f6168aedc3d" dependencies = [ "bindgen 0.71.1", "cc", diff --git a/components/script/script_runtime.rs b/components/script/script_runtime.rs index d82a6c126b7..c054ae6afef 100644 --- a/components/script/script_runtime.rs +++ b/components/script/script_runtime.rs @@ -88,6 +88,9 @@ static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps { getIncumbentGlobal: Some(get_incumbent_global), enqueuePromiseJob: Some(enqueue_promise_job), empty: Some(empty), + pushNewInterruptQueue: Some(push_new_interrupt_queue), + popInterruptQueue: Some(pop_interrupt_queue), + dropInterruptQueues: Some(drop_interrupt_queues), }; static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks { @@ -248,6 +251,41 @@ unsafe extern "C" fn empty(extra: *const c_void) -> bool { result } +#[allow(unsafe_code)] +unsafe extern "C" fn push_new_interrupt_queue(interrupt_queues: *mut c_void) -> *const c_void { + let mut result = std::ptr::null(); + wrap_panic(&mut || { + let mut interrupt_queues = Box::from_raw(interrupt_queues as *mut Vec>); + let new_queue = Rc::new(MicrotaskQueue::default()); + result = Rc::as_ptr(&new_queue) as *const c_void; + interrupt_queues.push(new_queue.clone()); + std::mem::forget(interrupt_queues); + }); + result +} + +#[allow(unsafe_code)] +unsafe extern "C" fn pop_interrupt_queue(interrupt_queues: *mut c_void) -> *const c_void { + let mut result = std::ptr::null(); + wrap_panic(&mut || { + let mut interrupt_queues = Box::from_raw(interrupt_queues as *mut Vec>); + let popped_queue: Rc = + interrupt_queues.pop().expect("Guaranteed by SpiderMonkey?"); + // Dangling, but jsglue.cpp will only use this for pointer comparison. + result = Rc::as_ptr(&popped_queue) as *const c_void; + std::mem::forget(interrupt_queues); + }); + result +} + +#[allow(unsafe_code)] +unsafe extern "C" fn drop_interrupt_queues(interrupt_queues: *mut c_void) { + wrap_panic(&mut || { + let interrupt_queues = Box::from_raw(interrupt_queues as *mut Vec>); + drop(interrupt_queues); + }); +} + /// SM callback for promise job resolution. Adds a promise callback to the current /// global's microtask queue. #[allow(unsafe_code)] @@ -474,6 +512,7 @@ pub(crate) fn notify_about_rejected_promises(global: &GlobalScope) { #[derive(JSTraceable)] pub(crate) struct Runtime { rt: RustRuntime, + /// Our actual microtask queue, which is preserved and untouched by the debugger when running debugger scripts. pub(crate) microtask_queue: Rc, job_queue: *mut JobQueue, networking_task_src: Option>, @@ -581,9 +620,16 @@ impl Runtime { InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error)); let microtask_queue = Rc::new(MicrotaskQueue::default()); + + // Extra queues for debugger scripts (“interrupts”) via AutoDebuggerJobQueueInterruption and saveJobQueue(). + // Moved indefinitely to mozjs via CreateJobQueue(), borrowed from mozjs via JobQueueTraps, and moved back from + // mozjs for dropping via DeleteJobQueue(). + let interrupt_queues: Box>> = Box::default(); + let job_queue = CreateJobQueue( &JOB_QUEUE_TRAPS, &*microtask_queue as *const _ as *const c_void, + Box::into_raw(interrupt_queues) as *mut c_void, ); SetJobQueue(cx, job_queue); SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut()); @@ -742,8 +788,10 @@ impl Runtime { impl Drop for Runtime { #[allow(unsafe_code)] fn drop(&mut self) { + // Clear our main microtask_queue. self.microtask_queue.clear(); + // Delete the RustJobQueue in mozjs, which will destroy our interrupt queues. unsafe { DeleteJobQueue(self.job_queue); }