#[repr(C)]pub struct AutoDebuggerJobQueueInterruption {
pub cx: *mut JSContext,
pub saved: u64,
}
Expand description
[SMDOC] Protecting the debuggee’s job/microtask queue from debugger activity.
When the JavaScript debugger interrupts the execution of some debuggee code (for a breakpoint, for example), the debuggee’s execution must be paused while the developer takes time to look at it. During this interruption, other tabs should remain active and usable. If the debuggee shares a main thread with non-debuggee tabs, that means that the thread will have to process non-debuggee HTML tasks and microtasks as usual, even as the debuggee’s are on hold until the debugger lets it continue execution. (Letting debuggee microtasks run during the interruption would mean that, from the debuggee’s point of view, their side effects would take place wherever the breakpoint was set - in general, not a place other code should ever run, and a violation of the run-to-completion rule.)
This means that, even though the timing and ordering of microtasks is carefully specified by the standard - and important to preserve for compatibility and predictability - debugger use may, correctly, have the effect of reordering microtasks. During the interruption, microtasks enqueued by non-debuggee tabs must run immediately alongside their HTML tasks as usual, whereas any debuggee microtasks that were in the queue when the interruption began must wait for the debuggee to be continued - and thus run after microtasks enqueued after they were.
Fortunately, this reordering is visible only at the global level: when implemented correctly, it is not detectable by an individual debuggee. Note that a debuggee should generally be a complete unit of similar-origin related browsing contexts. Since non-debuggee activity falls outside that unit, it should never be visible to the debuggee (except via mechanisms that are already asynchronous, like events), so the debuggee should be unable to detect non-debuggee microtasks running when they normally would not. As long as behavior visible to the debuggee is unaffected by the interruption, we have respected the spirit of the rule.
Of course, even as we accept the general principle that interrupting the debuggee should have as little detectable effect as possible, we still permit the developer to do things like evaluate expressions at the console that have arbitrary effects on the debuggee’s state—effects that could never occur naturally at that point in the program. But since these are explicitly requested by the developer, who presumably knows what they’re doing, we support this as best we can. If the developer evaluates an expression in the console that resolves a promise, it seems most natural for the promise’s reaction microtasks to run immediately, within the interruption. This is an ‘unnatural’ time for the microtasks to run, but no more unnatural than the evaluation that triggered them.
So the overall behavior we need is as follows:
-
When the debugger interrupts a debuggee, the debuggee’s microtask queue must be saved.
-
When debuggee execution resumes, the debuggee’s microtask queue must be restored exactly as it was when the interruption occurred.
-
Non-debuggee task and microtask execution must take place normally during the interruption.
Since each HTML task begins with an empty microtask queue, and it should not be possible for a task to mix debuggee and non-debuggee code, interrupting a debuggee should always find a microtask queue containing exclusively debuggee microtasks, if any. So saving and restoring the microtask queue should affect only the debuggee, not any non-debuggee content.
§AutoDebuggerJobQueueInterruption
AutoDebuggerJobQueueInterruption is an RAII class, meant for use by the Debugger API implementation, that takes care of saving and restoring the queue.
Constructing and initializing an instance of AutoDebuggerJobQueueInterruption sets aside the given JSContext’s job queue, leaving the JSContext’s queue empty. When the AutoDebuggerJobQueueInterruption instance is destroyed, it asserts that the JSContext’s current job queue (holding jobs enqueued while the AutoDebuggerJobQueueInterruption was alive) is empty, and restores the saved queue to the JSContext.
Since the Debugger API’s behavior is up to us, we can specify that Debugger hooks begin execution with an empty job queue, and that we drain the queue after each hook function has run. This drain will be visible to debugger hooks, and makes hook calls resemble HTML tasks, with their own automatic microtask checkpoint. But, the drain will be invisible to the debuggee, as its queue is preserved across the hook invocation.
To protect the debuggee’s job queue, Debugger takes care to invoke callback functions only within the scope of an AutoDebuggerJobQueueInterruption instance.
§Why not let the hook functions themselves take care of this?
Certainly, we could leave responsibility for saving and restoring the job queue to the Debugger hook functions themselves.
In fact, early versions of this change tried making the devtools server save and restore the queue explicitly, but because hooks are set and changed in numerous places, it was hard to be confident that every case had been covered, and it seemed that future changes could easily introduce new holes.
Later versions of this change modified the accessor properties on the Debugger objects’ prototypes to automatically protect the job queue when calling hooks, but the effect was essentially a monkeypatch applied to an API we defined and control, which doesn’t make sense.
In the end, since promises have become such a pervasive part of JavaScript programming, almost any imaginable use of Debugger would need to provide some kind of protection for the debuggee’s job queue, so it makes sense to simply handle it once, carefully, in the implementation of Debugger itself.
Fields§
§cx: *mut JSContext
§saved: u64
Trait Implementations§
source§impl PartialEq for AutoDebuggerJobQueueInterruption
impl PartialEq for AutoDebuggerJobQueueInterruption
source§fn eq(&self, other: &AutoDebuggerJobQueueInterruption) -> bool
fn eq(&self, other: &AutoDebuggerJobQueueInterruption) -> bool
self
and other
values to be equal, and is used by ==
.impl StructuralPartialEq for AutoDebuggerJobQueueInterruption
Auto Trait Implementations§
impl Freeze for AutoDebuggerJobQueueInterruption
impl RefUnwindSafe for AutoDebuggerJobQueueInterruption
impl !Send for AutoDebuggerJobQueueInterruption
impl !Sync for AutoDebuggerJobQueueInterruption
impl Unpin for AutoDebuggerJobQueueInterruption
impl UnwindSafe for AutoDebuggerJobQueueInterruption
Blanket Implementations§
source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
source§impl<T> Filterable for T
impl<T> Filterable for T
source§fn filterable(
self,
filter_name: &'static str,
) -> RequestFilterDataProvider<T, fn(_: DataRequest<'_>) -> bool>
fn filterable( self, filter_name: &'static str, ) -> RequestFilterDataProvider<T, fn(_: DataRequest<'_>) -> bool>
source§impl<T> Instrument for T
impl<T> Instrument for T
source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
source§impl<T> IntoEither for T
impl<T> IntoEither for T
source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left
is true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read moresource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self
into a Left
variant of Either<Self, Self>
if into_left(&self)
returns true
.
Converts self
into a Right
variant of Either<Self, Self>
otherwise. Read more