Skip to content

Instantly share code, notes, and snippets.

@sagudev
Last active January 24, 2026 08:05
Show Gist options
  • Select an option

  • Save sagudev/839382e70631b1eaed5a4d226b830b6e to your computer and use it in GitHub Desktop.

Select an option

Save sagudev/839382e70631b1eaed5a4d226b830b6e to your computer and use it in GitHub Desktop.
Guide for `&mut JSContext` refactor

Guide for &mut JSContext refactor

Types

There are 3 JSContext types:

  • js::jsapi::JSContext which is usually used as *mut RawContext
  • script_bindings::script_runtime::JSContext which is just wrapper for above pointer, typically named SafeJSContext
  • js::context::JSContext new JSContext, usually passed as &mut JSContext (GC can happen) or &JSContext (no GC can happen)

Function need to accept &mut JSContext (or something even more powerful, see Realms) if they get any of these arguments: SafeJSContext, RawJSContext, CanGc:

- fn f(cx: SafeJSContext, can_gc: CanGc) {
+ fn f(cx: &mut js::context::JSContext) {
-     another_fn_that_still_uses_old(cx.into(), CanGc::from_cx(cx));
+     another_fn_that_uses_raw_cx(unsafe { cx.raw_cx() });

prefer doing this inline instead of let can_gc = CanGc::from_cx(cx) as this makes further changes/inspection easier.

The task! macro supports passing &mut JSContext into closures, by naming first parameter cx:

- task!(PostConnectionSteps: |static_node_list: SmallVec<[DomRoot<Node>; 4]>| {
+ task!(PostConnectionSteps: |cx, static_node_list: SmallVec<[DomRoot<Node>; 4]>| {
-     vtable_for(&node).post_connection_steps(CanGc::note());
+     vtable_for(&node).post_connection_steps(CanGc::from_cx(cx));
  })

Realms

There are also two special JSContext variants which represents JSContext with entered realm:

  • js::realm::AutoRealm (to be used instead JSAutoRealm)
  • js::realm::CurrentRealm (analogue to InRealm)
- let _realm = JSAutoRealm::new(cx_ptr, target_obj);
+ let mut realm = AutoRealm::new(cx, NonNull::new(target).unwrap());
- let _realm = enter_realm(object);
+ let mut realm: AutoRealm = enter_auto_realm(cx, object);
+ let mut realm = realm.current_realm(); // if you need current realm
- let in_realm_proof = AlreadyInRealm::assert_for_cx(cx_ptr);
+ let mut realm = CurrentRealm::assert(cx);
+ let in_realm_proof = (&mut realm).into();
+ let in_realm_proof = (&mut realm).into();
+ InRealm::Already(&in_realm_proof)
- InRealm::AlreadyInRealm(&AlreadyInRealm)

Global

- let global = GlobalScope::from_context(cx_ptr, InRealm::Already(&in_realm_proof));
+ let global = GlobalScope::from_current_realm(realm);

Handles

There are two types of handles in mozjs:

// raw handles
use js::jsapi::{Handle as RawHandle, MutableHandle as RawMutableHandle};
// safe handles with lifetimes
use js::rust::{Handle, MutableHandle};

unfortunetly the deref methods of handles are unsound, so one should insted prefer using .as_ref(&cx) or .as_mut_ref(&cx) which will prevent any GC while reference to inner content is alive. Same methods also exists for RootedGuard (the type that gets created in rooted! macro).

conversions

conversion traits implements both safe and unsafe methods, prefer using safe methods:

to_jsval -> safe_to_jsval

from_jsval -> safe_from_jsval

Bindings

In bindings.conf one can specify one of these options (ordered by their power, prefer using the least powerful one as possible):

  1. cx_no_gc prepends argument &JSContext, which allows creating NoGC tokens and using functions that do not trigger GC.
  2. cx prepends argument &mut JSContext, which allows everything that previous one allows, but it also allows calling GC triggering functions.
  3. realm prepends argument &mut CurrentRealm, which can be deref_mut to &mut JSContext (so it can do everything that previous can), but it also ensures that there is current entered realm, which can be used for creation of InRealm.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment