script: Implement jsglue traps for saveJobQueue() (#38232)

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](b14aebff23/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](b14aebff23/mozjs-sys/src/jsglue.cpp (L83-L86)).

this patch bumps mozjs to servo/mozjs#595, which implements
[saveJobQueue() and
SavedJobQueue](b14aebff23/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](b14aebff23/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 <dazabani@igalia.com>
Co-authored-by: atbrakhi <atbrakhi@igalia.com>
This commit is contained in:
shuppy 2025-07-28 19:39:35 +08:00 committed by GitHub
parent f5ee72f89a
commit 9da4c74a60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 3 deletions

6
Cargo.lock generated
View file

@ -5240,7 +5240,7 @@ dependencies = [
[[package]] [[package]]
name = "mozjs" name = "mozjs"
version = "0.14.1" version = "0.14.1"
source = "git+https://github.com/servo/mozjs#9c017973a4bf9186df2335d8c96ed3554b84434e" source = "git+https://github.com/servo/mozjs#b23161580b082e1ccfa3273d94f43f6168aedc3d"
dependencies = [ dependencies = [
"bindgen 0.71.1", "bindgen 0.71.1",
"cc", "cc",
@ -5251,8 +5251,8 @@ dependencies = [
[[package]] [[package]]
name = "mozjs_sys" name = "mozjs_sys"
version = "0.128.13-1" version = "0.128.13-2"
source = "git+https://github.com/servo/mozjs#9c017973a4bf9186df2335d8c96ed3554b84434e" source = "git+https://github.com/servo/mozjs#b23161580b082e1ccfa3273d94f43f6168aedc3d"
dependencies = [ dependencies = [
"bindgen 0.71.1", "bindgen 0.71.1",
"cc", "cc",

View file

@ -88,6 +88,9 @@ static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
getIncumbentGlobal: Some(get_incumbent_global), getIncumbentGlobal: Some(get_incumbent_global),
enqueuePromiseJob: Some(enqueue_promise_job), enqueuePromiseJob: Some(enqueue_promise_job),
empty: Some(empty), empty: Some(empty),
pushNewInterruptQueue: Some(push_new_interrupt_queue),
popInterruptQueue: Some(pop_interrupt_queue),
dropInterruptQueues: Some(drop_interrupt_queues),
}; };
static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks { static SECURITY_CALLBACKS: JSSecurityCallbacks = JSSecurityCallbacks {
@ -248,6 +251,41 @@ unsafe extern "C" fn empty(extra: *const c_void) -> bool {
result 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<Rc<MicrotaskQueue>>);
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<Rc<MicrotaskQueue>>);
let popped_queue: Rc<MicrotaskQueue> =
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<Rc<MicrotaskQueue>>);
drop(interrupt_queues);
});
}
/// SM callback for promise job resolution. Adds a promise callback to the current /// SM callback for promise job resolution. Adds a promise callback to the current
/// global's microtask queue. /// global's microtask queue.
#[allow(unsafe_code)] #[allow(unsafe_code)]
@ -474,6 +512,7 @@ pub(crate) fn notify_about_rejected_promises(global: &GlobalScope) {
#[derive(JSTraceable)] #[derive(JSTraceable)]
pub(crate) struct Runtime { pub(crate) struct Runtime {
rt: RustRuntime, rt: RustRuntime,
/// Our actual microtask queue, which is preserved and untouched by the debugger when running debugger scripts.
pub(crate) microtask_queue: Rc<MicrotaskQueue>, pub(crate) microtask_queue: Rc<MicrotaskQueue>,
job_queue: *mut JobQueue, job_queue: *mut JobQueue,
networking_task_src: Option<Box<SendableTaskSource>>, networking_task_src: Option<Box<SendableTaskSource>>,
@ -581,9 +620,16 @@ impl Runtime {
InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error)); InitConsumeStreamCallback(cx, Some(consume_stream), Some(report_stream_error));
let microtask_queue = Rc::new(MicrotaskQueue::default()); 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<Vec<Rc<MicrotaskQueue>>> = Box::default();
let job_queue = CreateJobQueue( let job_queue = CreateJobQueue(
&JOB_QUEUE_TRAPS, &JOB_QUEUE_TRAPS,
&*microtask_queue as *const _ as *const c_void, &*microtask_queue as *const _ as *const c_void,
Box::into_raw(interrupt_queues) as *mut c_void,
); );
SetJobQueue(cx, job_queue); SetJobQueue(cx, job_queue);
SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut()); SetPromiseRejectionTrackerCallback(cx, Some(promise_rejection_tracker), ptr::null_mut());
@ -742,8 +788,10 @@ impl Runtime {
impl Drop for Runtime { impl Drop for Runtime {
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn drop(&mut self) { fn drop(&mut self) {
// Clear our main microtask_queue.
self.microtask_queue.clear(); self.microtask_queue.clear();
// Delete the RustJobQueue in mozjs, which will destroy our interrupt queues.
unsafe { unsafe {
DeleteJobQueue(self.job_queue); DeleteJobQueue(self.job_queue);
} }