Repository: servo/servo (language: python)

Issue:
Use a MutableHandleValue outparam for calls returning GCThings
This is a followup to https://github.com/servo/servo/pull/6150 .

DOM methods that return JSVal or JSObjects are better served by taking a MutableHandleValue to dump the return value into.



Discussion / hints:
Cc @asajeffrey, because linjs would make this issue obsolete.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 80348e43bd09a..d5ebd7da29a6f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4516,7 +4516,7 @@ dependencies = [
 [[package]]
 name = "mozjs"
 version = "0.14.1"
-source = "git+https://github.com/servo/mozjs#118aae2bcdb39617473b2cfe9fc680274ebbb9fc"
+source = "git+https://github.com/servo/mozjs#8526195dce129d2ccd83e7e1e5d03a954c11fafe"
 dependencies = [
  "bindgen",
  "cc",
@@ -4528,8 +4528,8 @@ dependencies = [
 
 [[package]]
 name = "mozjs_sys"
-version = "0.128.3-1"
-source = "git+https://github.com/servo/mozjs#118aae2bcdb39617473b2cfe9fc680274ebbb9fc"
+version = "0.128.3-4"
+source = "git+https://github.com/servo/mozjs#8526195dce129d2ccd83e7e1e5d03a954c11fafe"
 dependencies = [
  "bindgen",
  "cc",
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py
index 6b68c68dc9312..f716f4a8be137 100644
--- a/components/script/dom/bindings/codegen/CodegenRust.py
+++ b/components/script/dom/bindings/codegen/CodegenRust.py
@@ -1415,6 +1415,18 @@ def typeNeedsCx(type, retVal=False):
     return type.isAny() or type.isObject()
 
 
+def returnTypeNeedsOutparam(type):
+    if type.nullable():
+        type = type.inner
+    return type.isAny()
+
+
+def outparamTypeFromReturnType(type):
+    if type.isAny():
+        return "MutableHandleValue"
+    raise f"Don't know how to handle {type} as an outparam"
+
+
 # Returns a conversion behavior suitable for a type
 def getConversionConfigForType(type, isEnforceRange, isClamp, treatNullAs):
     if type.isSequence() or type.isRecord():
@@ -1502,8 +1514,6 @@ def getRetvalDeclarationForType(returnType, descriptorProvider):
         if returnType.nullable():
             result = CGWrapper(result, pre="Option<", post=">")
         return result
-    # TODO: Return the value through a MutableHandleValue outparam
-    # https://github.com/servo/servo/issues/6307
     if returnType.isAny():
         return CGGeneric("JSVal")
     if returnType.isObject() or returnType.isSpiderMonkeyInterface():
@@ -3705,6 +3715,12 @@ def __init__(self, errorResult, arguments, argsPre, returnType,
         isFallible = errorResult is not None
 
         result = getRetvalDeclarationForType(returnType, descriptor)
+        if returnType and returnTypeNeedsOutparam(returnType):
+            rootType = result
+            result = CGGeneric("()")
+        else:
+            rootType = None
+
         if isFallible:
             result = CGWrapper(result, pre="Result<", post=", Error>")
 
@@ -3723,10 +3739,19 @@ def __init__(self, errorResult, arguments, argsPre, returnType,
             args.append(CGGeneric("InRealm::already(&AlreadyInRealm::assert_for_cx(cx))"))
         if nativeMethodName in descriptor.canGcMethods:
             args.append(CGGeneric("CanGc::note()"))
+        if rootType:
+            args.append(CGGeneric("retval.handle_mut()"))
 
         # Build up our actual call
         self.cgRoot = CGList([], "\n")
 
+        if rootType:
+            self.cgRoot.append(CGList([
+                CGGeneric("rooted!(in(*cx) let mut retval: "),
+                rootType,
+                CGGeneric(");"),
+            ]))
+
         call = CGGeneric(nativeMethodName)
         if static:
             call = CGWrapper(call, pre=f"{MakeNativeName(descriptor.interface.identifier.name)}::")
@@ -3846,7 +3871,18 @@ def isFallible(self):
         return 'infallible' not in self.extendedAttributes
 
     def wrap_return_value(self):
-        return wrapForType('MutableHandleValue::from_raw(args.rval())', successCode='return true;')
+        resultName = "result"
+        # Maplike methods have `any` return values in WebIDL, but our internal bindings
+        # use stronger types so we need to exclude them from being handled like other
+        # generated code.
+        if returnTypeNeedsOutparam(self.returnType) and (
+           not (self.idlNode.isMethod() and self.idlNode.isMaplikeOrSetlikeOrIterableMethod())):
+            resultName = "retval"
+        return wrapForType(
+            'MutableHandleValue::from_raw(args.rval())',
+            result=resultName,
+            successCode='return true;',
+        )
 
     def define(self):
         return f"{self.cgRoot.define()}\n{self.wrap_return_value()}"
@@ -6303,8 +6339,8 @@ class CGInterfaceTrait(CGThing):
     def __init__(self, descriptor, descriptorProvider):
         CGThing.__init__(self)
 
-        def attribute_arguments(needCx, argument=None, inRealm=False, canGc=False):
-            if needCx:
+        def attribute_arguments(attribute_type, argument=None, inRealm=False, canGc=False, retval=False):
+            if typeNeedsCx(attribute_type, retval):
                 yield "cx", "SafeJSContext"
 
             if argument:
@@ -6316,6 +6352,9 @@ def attribute_arguments(needCx, argument=None, inRealm=False, canGc=False):
             if canGc:
                 yield "_can_gc", "CanGc"
 
+            if retval and returnTypeNeedsOutparam(attribute_type):
+                yield "retval", outparamTypeFromReturnType(attribute_type)
+
         def members():
             for m in descriptor.interface.members:
                 if (m.isMethod()
@@ -6335,9 +6374,10 @@ def members():
                     infallible = 'infallible' in descriptor.getExtendedAttributes(m, getter=True)
                     yield (name,
                            attribute_arguments(
-                               typeNeedsCx(m.type, True),
+                               m.type,
                                inRealm=name in descriptor.inRealmMethods,
-                               canGc=name in descriptor.canGcMethods
+                               canGc=name in descriptor.canGcMethods,
+                               retval=True
                            ),
                            return_type(descriptor, m.type, infallible),
                            m.isStatic())
@@ -6351,10 +6391,11 @@ def members():
                             rettype = "ErrorResult"
                         yield (name,
                                attribute_arguments(
-                                   typeNeedsCx(m.type, False),
+                                   m.type,
                                    m.type,
                                    inRealm=name in descriptor.inRealmMethods,
-                                   canGc=name in descriptor.canGcMethods
+                                   canGc=name in descriptor.canGcMethods,
+                                   retval=False,
                                ),
                                rettype,
                                m.isStatic())
@@ -7240,9 +7281,14 @@ def method_arguments(descriptorProvider, returnType, arguments, passJSBits=True,
     if canGc:
         yield "_can_gc", "CanGc"
 
+    if returnTypeNeedsOutparam(returnType):
+        yield "rval", outparamTypeFromReturnType(returnType),
+
 
 def return_type(descriptorProvider, rettype, infallible):
     result = getRetvalDeclarationForType(rettype, descriptorProvider)
+    if rettype and returnTypeNeedsOutparam(rettype):
+        result = CGGeneric("()")
     if not infallible:
         result = CGWrapper(result, pre="Fallible<", post=">")
     return result.define()
@@ -7466,6 +7512,7 @@ def __init__(self, sig, name, descriptorProvider, needThisHandling):
                     f"{self.argCount - 1} + {lastArg.identifier.name}.len()").removeprefix("0 + ")
             else:
                 self.argCountStr = f"{self.argCount}"
+        self.usingOutparam = returnTypeNeedsOutparam(self.retvalType)
         self.needThisHandling = needThisHandling
         # If needThisHandling, we generate ourselves as private and the caller
         # will handle generating public versions that handle the "this" stuff.
@@ -7518,15 +7565,16 @@ def getResultConversion(self):
         template = info.template
         declType = info.declType
 
-        convertType = instantiateJSToNativeConversionTemplate(
-            template, replacements, declType, "rvalDecl")
+        if self.usingOutparam:
+            convertType = CGGeneric("")
+        else:
+            convertType = instantiateJSToNativeConversionTemplate(
+                template, replacements, declType, "retval")
 
-        if self.retvalType is None or self.retvalType.isUndefined():
+        if self.retvalType is None or self.retvalType.isUndefined() or self.usingOutparam:
             retval = "()"
-        elif self.retvalType.isAny():
-            retval = "rvalDecl.get()"
         else:
-            retval = "rvalDecl"
+            retval = "retval"
 
         return f"{convertType.define()}\nOk({retval})\n"
 
@@ -7633,7 +7681,10 @@ def __init__(self, sig, name, descriptorProvider, needThisHandling):
                                 needThisHandling)
 
     def getRvalDecl(self):
-        return "rooted!(in(*cx) let mut rval = UndefinedValue());\n"
+        if self.usingOutparam:
+            return ""
+        else:
+            return "rooted!(in(*cx) let mut rval = UndefinedValue());\n"
 
     def getCall(self):
         if self.argCount > 0:
@@ -7642,6 +7693,7 @@ def getCall(self):
         else:
             argv = "ptr::null_mut()"
             argc = "0"
+        suffix = "" if self.usingOutparam else ".handle_mut()"
         return (f"{self.getCallableDecl()}"
                 f"rooted!(in(*cx) let rootedThis = {self.getThisObj()});\n"
                 f"let ok = {self.getCallGuard()}JS_CallFunctionValue(\n"
@@ -7649,7 +7701,7 @@ def getCall(self):
                 "    &HandleValueArray {\n"
                 f"        length_: {argc} as ::libc::size_t,\n"
                 f"        elements_: {argv}\n"
-                "    }, rval.handle_mut());\n"
+                f"    }}, rval{suffix});\n"
                 "maybe_resume_unwind();\n"
                 "if !ok {\n"
                 "    return Err(JSFailed);\n"
diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs
index c9cbbae2723de..cd7d8d4e6e5b7 100644
--- a/components/script/dom/bindings/utils.rs
+++ b/components/script/dom/bindings/utils.rs
@@ -117,13 +117,15 @@ impl Clone for DOMJSClass {
 unsafe impl Sync for DOMJSClass {}
 
 /// Returns a JSVal representing the frozen JavaScript array
-pub fn to_frozen_array<T: ToJSValConvertible>(convertibles: &[T], cx: SafeJSContext) -> JSVal {
-    rooted!(in(*cx) let mut ports = UndefinedValue());
-    unsafe { convertibles.to_jsval(*cx, ports.handle_mut()) };
+pub fn to_frozen_array<T: ToJSValConvertible>(
+    convertibles: &[T],
+    cx: SafeJSContext,
+    rval: MutableHandleValue,
+) {
+    unsafe { convertibles.to_jsval(*cx, rval) };
 
-    rooted!(in(*cx) let obj = ports.to_object());
+    rooted!(in(*cx) let obj = rval.to_object());
     unsafe { JS_FreezeObject(*cx, RawHandleObject::from(obj.handle())) };
-    *ports
 }
 
 /// Returns the ProtoOrIfaceArray for the given global object.
diff --git a/components/script/dom/csslayerstatementrule.rs b/components/script/dom/csslayerstatementrule.rs
index 9a4aa5a0dc917..dd9e632701d25 100644
--- a/components/script/dom/csslayerstatementrule.rs
+++ b/components/script/dom/csslayerstatementrule.rs
@@ -3,7 +3,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 use servo_arc::Arc;
 use style::shared_lock::ToCssWithGuard;
 use style::stylesheets::{CssRuleType, LayerStatementRule};
@@ -67,13 +67,13 @@ impl SpecificCSSRule for CSSLayerStatementRule {
 
 impl CSSLayerStatementRuleMethods for CSSLayerStatementRule {
     /// <https://drafts.csswg.org/css-cascade-5/#dom-csslayerstatementrule-namelist>
-    fn NameList(&self, cx: SafeJSContext) -> JSVal {
+    fn NameList(&self, cx: SafeJSContext, retval: MutableHandleValue) {
         let names: Vec<DOMString> = self
             .layerstatementrule
             .names
             .iter()
             .map(|name| DOMString::from_string(name.to_css_string()))
             .collect();
-        to_frozen_array(names.as_slice(), cx)
+        to_frozen_array(names.as_slice(), cx, retval)
     }
 }
diff --git a/components/script/dom/customelementregistry.rs b/components/script/dom/customelementregistry.rs
index dfbbe5e5d79b7..dd8675b066da3 100644
--- a/components/script/dom/customelementregistry.rs
+++ b/components/script/dom/customelementregistry.rs
@@ -548,16 +548,12 @@ impl CustomElementRegistryMethods for CustomElementRegistry {
 
     /// <https://html.spec.whatwg.org/multipage/#dom-customelementregistry-get>
     #[allow(unsafe_code)]
-    fn Get(&self, cx: JSContext, name: DOMString) -> JSVal {
+    fn Get(&self, cx: JSContext, name: DOMString, mut retval: MutableHandleValue) {
         match self.definitions.borrow().get(&LocalName::from(&*name)) {
             Some(definition) => unsafe {
-                rooted!(in(*cx) let mut constructor = UndefinedValue());
-                definition
-                    .constructor
-                    .to_jsval(*cx, constructor.handle_mut());
-                constructor.get()
+                definition.constructor.to_jsval(*cx, retval);
             },
-            None => UndefinedValue(),
+            None => retval.set(UndefinedValue()),
         }
     }
 
@@ -994,7 +990,13 @@ impl CustomElementReaction {
                     .iter()
                     .map(|arg| unsafe { HandleValue::from_raw(arg.handle()) })
                     .collect();
-                let _ = callback.Call_(element, arguments, ExceptionHandling::Report);
+                rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
+                let _ = callback.Call_(
+                    element,
+                    arguments,
+                    value.handle_mut(),
+                    ExceptionHandling::Report,
+                );
             },
         }
     }
diff --git a/components/script/dom/customevent.rs b/components/script/dom/customevent.rs
index cb99468e00edc..459747b6ca416 100644
--- a/components/script/dom/customevent.rs
+++ b/components/script/dom/customevent.rs
@@ -5,7 +5,7 @@
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::codegen::Bindings::CustomEventBinding;
@@ -105,8 +105,8 @@ impl CustomEventMethods for CustomEvent {
     }
 
     // https://dom.spec.whatwg.org/#dom-customevent-detail
-    fn Detail(&self, _cx: JSContext) -> JSVal {
-        self.detail.get()
+    fn Detail(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.detail.get())
     }
 
     // https://dom.spec.whatwg.org/#dom-customevent-initcustomevent
diff --git a/components/script/dom/dissimilaroriginwindow.rs b/components/script/dom/dissimilaroriginwindow.rs
index 51cad3692825a..65a8039e26197 100644
--- a/components/script/dom/dissimilaroriginwindow.rs
+++ b/components/script/dom/dissimilaroriginwindow.rs
@@ -5,8 +5,8 @@
 use base::id::PipelineId;
 use dom_struct::dom_struct;
 use js::jsapi::{Heap, JSObject};
-use js::jsval::{JSVal, UndefinedValue};
-use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue};
+use js::jsval::UndefinedValue;
+use js::rust::{CustomAutoRooter, CustomAutoRooterGuard, HandleValue, MutableHandleValue};
 use script_traits::{ScriptMsg, StructuredSerializedData};
 use servo_url::ServoUrl;
 
@@ -167,9 +167,9 @@ impl DissimilarOriginWindowMethods for DissimilarOriginWindow {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-opener
-    fn Opener(&self, _: JSContext) -> JSVal {
+    fn Opener(&self, _: JSContext, mut retval: MutableHandleValue) {
         // TODO: Implement x-origin opener
-        UndefinedValue()
+        retval.set(UndefinedValue());
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-opener
diff --git a/components/script/dom/errorevent.rs b/components/script/dom/errorevent.rs
index 012a2cde343bd..1bd0f9f3af86d 100644
--- a/components/script/dom/errorevent.rs
+++ b/components/script/dom/errorevent.rs
@@ -7,7 +7,7 @@ use std::cell::Cell;
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::cell::DomRefCell;
@@ -166,8 +166,8 @@ impl ErrorEventMethods for ErrorEvent {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-errorevent-error
-    fn Error(&self, _cx: JSContext) -> JSVal {
-        self.error.get()
+    fn Error(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.error.get());
     }
 
     // https://dom.spec.whatwg.org/#dom-event-istrusted
diff --git a/components/script/dom/eventtarget.rs b/components/script/dom/eventtarget.rs
index 98d1b9124164c..b097080bbc4e3 100644
--- a/components/script/dom/eventtarget.rs
+++ b/components/script/dom/eventtarget.rs
@@ -15,6 +15,7 @@ use deny_public_fields::DenyPublicFields;
 use dom_struct::dom_struct;
 use fnv::FnvHasher;
 use js::jsapi::JS_GetFunctionObject;
+use js::jsval::JSVal;
 use js::rust::wrappers::CompileFunction;
 use js::rust::{
     transform_u16_to_source_text, CompileOptionsWrapper, HandleObject, RootedObjectVectorWrapper,
@@ -194,7 +195,9 @@ impl CompiledEventListener {
                         if let Some(event) = event.downcast::<ErrorEvent>() {
                             if object.is::<Window>() || object.is::<WorkerGlobalScope>() {
                                 let cx = GlobalScope::get_cx();
-                                rooted!(in(*cx) let error = event.Error(cx));
+                                rooted!(in(*cx) let mut error: JSVal);
+                                event.Error(cx, error.handle_mut());
+                                rooted!(in(*cx) let mut rooted_return_value: JSVal);
                                 let return_value = handler.Call_(
                                     object,
                                     EventOrString::String(event.Message()),
@@ -202,13 +205,13 @@ impl CompiledEventListener {
                                     Some(event.Lineno()),
                                     Some(event.Colno()),
                                     Some(error.handle()),
+                                    rooted_return_value.handle_mut(),
                                     exception_handle,
                                 );
                                 // Step 4
-                                if let Ok(return_value) = return_value {
-                                    rooted!(in(*cx) let return_value = return_value);
-                                    if return_value.handle().is_boolean() &&
-                                        return_value.handle().to_boolean()
+                                if let Ok(()) = return_value {
+                                    if rooted_return_value.handle().is_boolean() &&
+                                        rooted_return_value.handle().to_boolean()
                                     {
                                         event.upcast::<Event>().PreventDefault();
                                     }
@@ -217,6 +220,7 @@ impl CompiledEventListener {
                             }
                         }
 
+                        rooted!(in(*GlobalScope::get_cx()) let mut rooted_return_value: JSVal);
                         let _ = handler.Call_(
                             object,
                             EventOrString::Event(DomRoot::from_ref(event)),
@@ -224,6 +228,7 @@ impl CompiledEventListener {
                             None,
                             None,
                             None,
+                            rooted_return_value.handle_mut(),
                             exception_handle,
                         );
                     },
@@ -250,10 +255,15 @@ impl CompiledEventListener {
                     },
 
                     CommonEventHandler::EventHandler(ref handler) => {
-                        if let Ok(value) = handler.Call_(object, event, exception_handle) {
-                            let cx = GlobalScope::get_cx();
-                            rooted!(in(*cx) let value = value);
-                            let value = value.handle();
+                        let cx = GlobalScope::get_cx();
+                        rooted!(in(*cx) let mut rooted_return_value: JSVal);
+                        if let Ok(()) = handler.Call_(
+                            object,
+                            event,
+                            rooted_return_value.handle_mut(),
+                            exception_handle,
+                        ) {
+                            let value = rooted_return_value.handle();
 
                             //Step 5
                             let should_cancel = value.is_boolean() && !value.to_boolean();
diff --git a/components/script/dom/extendablemessageevent.rs b/components/script/dom/extendablemessageevent.rs
index 6b08077e77d34..c4f0ccb824925 100644
--- a/components/script/dom/extendablemessageevent.rs
+++ b/components/script/dom/extendablemessageevent.rs
@@ -5,7 +5,7 @@
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::cell::DomRefCell;
@@ -188,8 +188,8 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent {
     }
 
     /// <https://w3c.github.io/ServiceWorker/#dom-extendablemessageevent-data>
-    fn Data(&self, _cx: JSContext) -> JSVal {
-        self.data.get()
+    fn Data(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.data.get())
     }
 
     /// <https://w3c.github.io/ServiceWorker/#dom-extendablemessageevent-origin>
@@ -208,9 +208,10 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent {
     }
 
     /// <https://w3c.github.io/ServiceWorker/#extendablemessage-event-ports>
-    fn Ports(&self, cx: JSContext) -> JSVal {
+    fn Ports(&self, cx: JSContext, mut retval: MutableHandleValue) {
         if let Some(ports) = &*self.frozen_ports.borrow() {
-            return ports.get();
+            retval.set(ports.get());
+            return;
         }
 
         let ports: Vec<DomRoot<MessagePort>> = self
@@ -218,7 +219,7 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent {
             .iter()
             .map(|port| DomRoot::from_ref(&**port))
             .collect();
-        let frozen_ports = to_frozen_array(ports.as_slice(), cx);
+        to_frozen_array(ports.as_slice(), cx, retval);
 
         // Safety: need to create the Heap value in its final memory location before setting it.
         *self.frozen_ports.borrow_mut() = Some(Heap::default());
@@ -226,8 +227,6 @@ impl ExtendableMessageEventMethods for ExtendableMessageEvent {
             .borrow()
             .as_ref()
             .unwrap()
-            .set(frozen_ports);
-
-        frozen_ports
+            .set(retval.get());
     }
 }
diff --git a/components/script/dom/gamepadhapticactuator.rs b/components/script/dom/gamepadhapticactuator.rs
index 3f7513b29ce3b..10be49dcfee9d 100644
--- a/components/script/dom/gamepadhapticactuator.rs
+++ b/components/script/dom/gamepadhapticactuator.rs
@@ -9,7 +9,7 @@ use dom_struct::dom_struct;
 use embedder_traits::{DualRumbleEffectParams, EmbedderMsg};
 use ipc_channel::ipc;
 use ipc_channel::router::ROUTER;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 use script_traits::GamepadSupportedHapticEffects;
 
 use crate::dom::bindings::cell::DomRefCell;
@@ -134,8 +134,8 @@ impl GamepadHapticActuator {
 
 impl GamepadHapticActuatorMethods for GamepadHapticActuator {
     /// <https://www.w3.org/TR/gamepad/#dom-gamepadhapticactuator-effects>
-    fn Effects(&self, cx: JSContext) -> JSVal {
-        to_frozen_array(self.effects.as_slice(), cx)
+    fn Effects(&self, cx: JSContext, retval: MutableHandleValue) {
+        to_frozen_array(self.effects.as_slice(), cx, retval)
     }
 
     /// <https://www.w3.org/TR/gamepad/#dom-gamepadhapticactuator-playeffect>
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index d18813a74f8ba..4cac0ca870f43 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -3072,16 +3072,21 @@ impl GlobalScope {
     }
 
     /// <https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute>
-    pub fn supported_performance_entry_types(&self, cx: SafeJSContext) -> JSVal {
+    pub fn supported_performance_entry_types(
+        &self,
+        cx: SafeJSContext,
+        mut retval: MutableHandleValue,
+    ) {
         if let Some(types) = &*self.frozen_supported_performance_entry_types.borrow() {
-            return types.get();
+            retval.set(types.get());
+            return;
         }
 
         let types: Vec<DOMString> = VALID_ENTRY_TYPES
             .iter()
             .map(|t| DOMString::from(t.to_string()))
             .collect();
-        let frozen_types = to_frozen_array(types.as_slice(), cx);
+        to_frozen_array(types.as_slice(), cx, retval);
 
         // Safety: need to create the Heap value in its final memory location before setting it.
         *self.frozen_supported_performance_entry_types.borrow_mut() = Some(Heap::default());
@@ -3089,9 +3094,7 @@ impl GlobalScope {
             .borrow()
             .as_ref()
             .unwrap()
-            .set(frozen_types);
-
-        frozen_types
+            .set(retval.get());
     }
 
     pub fn is_headless(&self) -> bool {
@@ -3372,7 +3375,8 @@ impl GlobalScope {
         cx: SafeJSContext,
         value: HandleValue,
         options: RootedTraceableBox<StructuredSerializeOptions>,
-    ) -> Fallible<js::jsval::JSVal> {
+        retval: MutableHandleValue,
+    ) -> Fallible<()> {
         let mut rooted = CustomAutoRooter::new(
             options
                 .transfer
@@ -3384,12 +3388,9 @@ impl GlobalScope {
 
         let data = structuredclone::write(cx, value, Some(guard))?;
 
-        rooted!(in(*cx) let mut message_clone = UndefinedValue());
-
-        structuredclone::read(self, data, message_clone.handle_mut())
-            .map_err(|_| Error::DataClone)?;
+        structuredclone::read(self, data, retval).map_err(|_| Error::DataClone)?;
 
-        Ok(message_clone.get())
+        Ok(())
     }
 
     pub(crate) fn fetch<Listener: FetchResponseListener + PreInvoke + Send + 'static>(
diff --git a/components/script/dom/gpucompilationinfo.rs b/components/script/dom/gpucompilationinfo.rs
index ee0049260db9f..a54b3e81e41c4 100644
--- a/components/script/dom/gpucompilationinfo.rs
+++ b/components/script/dom/gpucompilationinfo.rs
@@ -3,7 +3,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 use webgpu::ShaderCompilationInfo;
 
 use super::bindings::codegen::Bindings::WebGPUBinding::GPUCompilationInfoMethods;
@@ -58,7 +58,7 @@ impl GPUCompilationInfo {
 
 impl GPUCompilationInfoMethods for GPUCompilationInfo {
     /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationinfo-messages>
-    fn Messages(&self, cx: JSContext) -> JSVal {
-        to_frozen_array(self.msg.as_slice(), cx)
+    fn Messages(&self, cx: JSContext, retval: MutableHandleValue) {
+        to_frozen_array(self.msg.as_slice(), cx, retval)
     }
 }
diff --git a/components/script/dom/history.rs b/components/script/dom/history.rs
index 67354cb0dc994..039bf15d5ed86 100644
--- a/components/script/dom/history.rs
+++ b/components/script/dom/history.rs
@@ -9,7 +9,7 @@ use base::id::HistoryStateId;
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::{JSVal, NullValue, UndefinedValue};
-use js::rust::HandleValue;
+use js::rust::{HandleValue, MutableHandleValue};
 use net_traits::{CoreResourceMsg, IpcSend};
 use profile_traits::ipc;
 use profile_traits::ipc::channel;
@@ -289,11 +289,12 @@ impl History {
 
 impl HistoryMethods for History {
     /// <https://html.spec.whatwg.org/multipage/#dom-history-state>
-    fn GetState(&self, _cx: JSContext) -> Fallible<JSVal> {
+    fn GetState(&self, _cx: JSContext, mut retval: MutableHandleValue) -> Fallible<()> {
         if !self.window.Document().is_fully_active() {
             return Err(Error::Security);
         }
-        Ok(self.state.get())
+        retval.set(self.state.get());
+        Ok(())
     }
 
     /// <https://html.spec.whatwg.org/multipage/#dom-history-length>
diff --git a/components/script/dom/intersectionobserver.rs b/components/script/dom/intersectionobserver.rs
index 3c02238f52fb7..7b926e4909a9e 100644
--- a/components/script/dom/intersectionobserver.rs
+++ b/components/script/dom/intersectionobserver.rs
@@ -5,8 +5,7 @@
 use std::rc::Rc;
 
 use dom_struct::dom_struct;
-use js::jsval::{JSVal, NullValue};
-use js::rust::HandleObject;
+use js::rust::{HandleObject, MutableHandleValue};
 
 use super::bindings::codegen::Bindings::IntersectionObserverBinding::{
     IntersectionObserverCallback, IntersectionObserverMethods,
@@ -93,9 +92,7 @@ impl IntersectionObserverMethods for IntersectionObserver {
     /// > constructor, or the sequence is empty, the value of this attribute will be [0].
     ///
     /// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-thresholds>
-    fn Thresholds(&self, _context: JSContext) -> JSVal {
-        NullValue()
-    }
+    fn Thresholds(&self, _context: JSContext, _retval: MutableHandleValue) {}
 
     /// > A number indicating the minimum delay in milliseconds between notifications from
     /// > this observer for a given target.
diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs
index 52731fd654bdd..f5268e72945e3 100644
--- a/components/script/dom/macros.rs
+++ b/components/script/dom/macros.rs
@@ -666,14 +666,3 @@ macro_rules! impl_rare_data (
         }
     );
 );
-
-#[macro_export]
-macro_rules! optional_root_object_to_js_or_null {
-    ($cx: expr, $binding:expr) => {{
-        rooted!(in($cx) let mut rval = NullValue());
-        if let Some(object) = $binding {
-            object.to_jsval($cx, rval.handle_mut());
-        }
-        rval.get()
-    }};
-}
diff --git a/components/script/dom/messageevent.rs b/components/script/dom/messageevent.rs
index 99f8603973ecf..004c80aad310d 100644
--- a/components/script/dom/messageevent.rs
+++ b/components/script/dom/messageevent.rs
@@ -5,7 +5,7 @@
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::cell::DomRefCell;
@@ -266,8 +266,8 @@ impl MessageEventMethods for MessageEvent {
     }
 
     /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-data>
-    fn Data(&self, _cx: JSContext) -> JSVal {
-        self.data.get()
+    fn Data(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.data.get())
     }
 
     /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-origin>
@@ -302,9 +302,10 @@ impl MessageEventMethods for MessageEvent {
     }
 
     /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-ports>
-    fn Ports(&self, cx: JSContext) -> JSVal {
+    fn Ports(&self, cx: JSContext, mut retval: MutableHandleValue) {
         if let Some(ports) = &*self.frozen_ports.borrow() {
-            return ports.get();
+            retval.set(ports.get());
+            return;
         }
 
         let ports: Vec<DomRoot<MessagePort>> = self
@@ -313,7 +314,7 @@ impl MessageEventMethods for MessageEvent {
             .iter()
             .map(|port| DomRoot::from_ref(&**port))
             .collect();
-        let frozen_ports = to_frozen_array(ports.as_slice(), cx);
+        to_frozen_array(ports.as_slice(), cx, retval);
 
         // Safety: need to create the Heap value in its final memory location before setting it.
         *self.frozen_ports.borrow_mut() = Some(Heap::default());
@@ -321,9 +322,7 @@ impl MessageEventMethods for MessageEvent {
             .borrow()
             .as_ref()
             .unwrap()
-            .set(frozen_ports);
-
-        frozen_ports
+            .set(retval.get());
     }
 
     /// <https://html.spec.whatwg.org/multipage/#dom-messageevent-initmessageevent>
diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs
index 9ef543bfb6805..969f006dd768a 100644
--- a/components/script/dom/navigator.rs
+++ b/components/script/dom/navigator.rs
@@ -7,7 +7,7 @@ use std::convert::TryInto;
 use std::sync::LazyLock;
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 
 use crate::dom::bindings::cell::DomRefCell;
 use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
@@ -199,8 +199,8 @@ impl NavigatorMethods for Navigator {
 
     // https://html.spec.whatwg.org/multipage/#dom-navigator-languages
     #[allow(unsafe_code)]
-    fn Languages(&self, cx: JSContext) -> JSVal {
-        to_frozen_array(&[self.Language()], cx)
+    fn Languages(&self, cx: JSContext, retval: MutableHandleValue) {
+        to_frozen_array(&[self.Language()], cx, retval)
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-navigator-plugins
diff --git a/components/script/dom/performanceobserver.rs b/components/script/dom/performanceobserver.rs
index 339941ffbf675..f6d83d496ddea 100644
--- a/components/script/dom/performanceobserver.rs
+++ b/components/script/dom/performanceobserver.rs
@@ -6,8 +6,7 @@ use std::cell::Cell;
 use std::rc::Rc;
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
-use js::rust::HandleObject;
+use js::rust::{HandleObject, MutableHandleValue};
 
 use crate::dom::bindings::callback::ExceptionHandling;
 use crate::dom::bindings::cell::DomRefCell;
@@ -137,10 +136,10 @@ impl PerformanceObserverMethods for PerformanceObserver {
     }
 
     // https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute
-    fn SupportedEntryTypes(cx: JSContext, global: &GlobalScope) -> JSVal {
+    fn SupportedEntryTypes(cx: JSContext, global: &GlobalScope, retval: MutableHandleValue) {
         // While this is exposed through a method of PerformanceObserver,
         // it is specified as associated with the global scope.
-        global.supported_performance_entry_types(cx)
+        global.supported_performance_entry_types(cx, retval)
     }
 
     // https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe()
diff --git a/components/script/dom/popstateevent.rs b/components/script/dom/popstateevent.rs
index d241c27311610..d0a8afe2048a8 100644
--- a/components/script/dom/popstateevent.rs
+++ b/components/script/dom/popstateevent.rs
@@ -5,7 +5,7 @@
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
@@ -102,8 +102,8 @@ impl PopStateEventMethods for PopStateEvent {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-popstateevent-state
-    fn State(&self, _cx: JSContext) -> JSVal {
-        self.state.get()
+    fn State(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.state.get())
     }
 
     // https://dom.spec.whatwg.org/#dom-event-istrusted
diff --git a/components/script/dom/promiserejectionevent.rs b/components/script/dom/promiserejectionevent.rs
index 7aec0e2cd4993..1a45790b7e1ed 100644
--- a/components/script/dom/promiserejectionevent.rs
+++ b/components/script/dom/promiserejectionevent.rs
@@ -8,7 +8,7 @@ use std::rc::Rc;
 use dom_struct::dom_struct;
 use js::jsapi::{Heap, JSObject};
 use js::jsval::JSVal;
-use js::rust::{HandleObject, HandleValue};
+use js::rust::{HandleObject, HandleValue, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
@@ -127,8 +127,8 @@ impl PromiseRejectionEventMethods for PromiseRejectionEvent {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-promiserejectionevent-reason
-    fn Reason(&self, _cx: JSContext) -> JSVal {
-        self.reason.get()
+    fn Reason(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.reason.get())
     }
 
     // https://dom.spec.whatwg.org/#dom-event-istrusted
diff --git a/components/script/dom/resizeobserverentry.rs b/components/script/dom/resizeobserverentry.rs
index 5d94db2ababd7..a5017c4868c0b 100644
--- a/components/script/dom/resizeobserverentry.rs
+++ b/components/script/dom/resizeobserverentry.rs
@@ -3,7 +3,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 
 use crate::dom::bindings::codegen::Bindings::ResizeObserverEntryBinding::ResizeObserverEntryMethods;
 use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
@@ -90,32 +90,32 @@ impl ResizeObserverEntryMethods for ResizeObserverEntry {
     }
 
     /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-borderboxsize>
-    fn BorderBoxSize(&self, cx: SafeJSContext) -> JSVal {
+    fn BorderBoxSize(&self, cx: SafeJSContext, retval: MutableHandleValue) {
         let sizes: Vec<DomRoot<ResizeObserverSize>> = self
             .border_box_size
             .iter()
             .map(|size| DomRoot::from_ref(&**size))
             .collect();
-        to_frozen_array(sizes.as_slice(), cx)
+        to_frozen_array(sizes.as_slice(), cx, retval)
     }
 
     /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentboxsize>
-    fn ContentBoxSize(&self, cx: SafeJSContext) -> JSVal {
+    fn ContentBoxSize(&self, cx: SafeJSContext, retval: MutableHandleValue) {
         let sizes: Vec<DomRoot<ResizeObserverSize>> = self
             .content_box_size
             .iter()
             .map(|size| DomRoot::from_ref(&**size))
             .collect();
-        to_frozen_array(sizes.as_slice(), cx)
+        to_frozen_array(sizes.as_slice(), cx, retval);
     }
 
     /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-devicepixelcontentboxsize>
-    fn DevicePixelContentBoxSize(&self, cx: SafeJSContext) -> JSVal {
+    fn DevicePixelContentBoxSize(&self, cx: SafeJSContext, retval: MutableHandleValue) {
         let sizes: Vec<DomRoot<ResizeObserverSize>> = self
             .device_pixel_content_box_size
             .iter()
             .map(|size| DomRoot::from_ref(&**size))
             .collect();
-        to_frozen_array(sizes.as_slice(), cx)
+        to_frozen_array(sizes.as_slice(), cx, retval);
     }
 }
diff --git a/components/script/dom/webgl2renderingcontext.rs b/components/script/dom/webgl2renderingcontext.rs
index 038f00df9df06..ffe7dec15a502 100644
--- a/components/script/dom/webgl2renderingcontext.rs
+++ b/components/script/dom/webgl2renderingcontext.rs
@@ -16,11 +16,8 @@ use dom_struct::dom_struct;
 use euclid::default::{Point2D, Rect, Size2D};
 use ipc_channel::ipc::{self, IpcSharedMemory};
 use js::jsapi::{JSObject, Type};
-use js::jsval::{
-    BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, ObjectValue, UInt32Value,
-    UndefinedValue,
-};
-use js::rust::{CustomAutoRooterGuard, HandleObject};
+use js::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, UInt32Value};
+use js::rust::{CustomAutoRooterGuard, HandleObject, MutableHandleValue};
 use js::typedarray::{ArrayBufferView, CreateWith, Float32, Int32Array, Uint32, Uint32Array};
 use script_layout_interface::HTMLCanvasDataSource;
 use servo_config::pref;
@@ -591,14 +588,20 @@ impl WebGL2RenderingContext {
     }
 
     #[allow(unsafe_code)]
-    fn get_default_fb_attachment_param(&self, attachment: u32, pname: u32) -> WebGLResult<JSVal> {
+    fn get_default_fb_attachment_param(
+        &self,
+        attachment: u32,
+        pname: u32,
+        mut retval: MutableHandleValue,
+    ) -> WebGLResult<()> {
         match attachment {
             constants::BACK | constants::DEPTH | constants::STENCIL => {},
             _ => return Err(InvalidEnum),
         }
 
         if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME {
-            return Ok(NullValue());
+            retval.set(NullValue());
+            return Ok(());
         }
 
         let attrs = self
@@ -646,7 +649,8 @@ impl WebGL2RenderingContext {
             },
             _ => return Err(InvalidEnum),
         };
-        Ok(Int32Value(intval))
+        retval.set(Int32Value(intval));
+        Ok(())
     }
 
     #[allow(unsafe_code)]
@@ -657,7 +661,8 @@ impl WebGL2RenderingContext {
         target: u32,
         attachment: u32,
         pname: u32,
-    ) -> WebGLResult<JSVal> {
+        mut rval: MutableHandleValue,
+    ) -> WebGLResult<()> {
         use crate::dom::webglframebuffer::WebGLFramebufferAttachmentRoot::{Renderbuffer, Texture};
 
         match attachment {
@@ -692,17 +697,16 @@ impl WebGL2RenderingContext {
         };
 
         if pname == constants::FRAMEBUFFER_ATTACHMENT_OBJECT_NAME {
-            rooted!(in(*cx) let mut rval = NullValue());
             match fb.attachment(attachment) {
                 Some(Renderbuffer(rb)) => unsafe {
-                    rb.to_jsval(*cx, rval.handle_mut());
+                    rb.to_jsval(*cx, rval);
                 },
                 Some(Texture(texture)) => unsafe {
-                    texture.to_jsval(*cx, rval.handle_mut());
+                    texture.to_jsval(*cx, rval);
                 },
-                _ => {},
+                _ => rval.set(NullValue()),
             }
-            return Ok(rval.get());
+            return Ok(());
         }
 
         match pname {
@@ -738,7 +742,8 @@ impl WebGL2RenderingContext {
             ));
 
         let retval = receiver.recv().unwrap();
-        Ok(Int32Value(retval))
+        rval.set(Int32Value(retval));
+        Ok(())
     }
 
     fn clearbuffer_array_size(&self, buffer: u32, draw_buffer: i32) -> WebGLResult<usize> {
@@ -931,101 +936,108 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5>
-    fn GetBufferParameter(&self, _cx: JSContext, target: u32, parameter: u32) -> JSVal {
-        let buffer =
-            handle_potential_webgl_error!(self.base, self.bound_buffer(target), return NullValue());
-        self.base.get_buffer_param(buffer, parameter)
+    fn GetBufferParameter(
+        &self,
+        _cx: JSContext,
+        target: u32,
+        parameter: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        let buffer = handle_potential_webgl_error!(
+            self.base,
+            self.bound_buffer(target),
+            return retval.set(NullValue())
+        );
+        self.base.get_buffer_param(buffer, parameter, retval)
     }
 
     #[allow(unsafe_code)]
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
-    fn GetParameter(&self, cx: JSContext, parameter: u32) -> JSVal {
+    fn GetParameter(&self, cx: JSContext, parameter: u32, mut rval: MutableHandleValue) {
         match parameter {
             constants::VERSION => unsafe {
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                "WebGL 2.0".to_jsval(*cx, rval.handle_mut());
-                return rval.get();
+                "WebGL 2.0".to_jsval(*cx, rval);
+                return;
             },
             constants::SHADING_LANGUAGE_VERSION => unsafe {
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                "WebGL GLSL ES 3.00".to_jsval(*cx, rval.handle_mut());
-                return rval.get();
+                "WebGL GLSL ES 3.00".to_jsval(*cx, rval);
+                return;
             },
             constants::MAX_CLIENT_WAIT_TIMEOUT_WEBGL => {
-                return DoubleValue(
-                    self.base.limits().max_client_wait_timeout_webgl.as_nanos() as f64
-                );
+                rval.set(DoubleValue(
+                    self.base.limits().max_client_wait_timeout_webgl.as_nanos() as f64,
+                ));
+                return;
             },
             constants::MAX_SERVER_WAIT_TIMEOUT => {
-                return DoubleValue(self.base.limits().max_server_wait_timeout.as_nanos() as f64);
+                rval.set(DoubleValue(
+                    self.base.limits().max_server_wait_timeout.as_nanos() as f64,
+                ));
+                return;
             },
             constants::SAMPLER_BINDING => unsafe {
                 let idx = (self.base.textures().active_unit_enum() - constants::TEXTURE0) as usize;
                 assert!(idx < self.samplers.len());
                 let sampler = self.samplers[idx].get();
-                return optional_root_object_to_js_or_null!(*cx, sampler);
+                sampler.to_jsval(*cx, rval);
+                return;
             },
             constants::COPY_READ_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_copy_read_buffer.get()
-                );
+                self.bound_copy_read_buffer.get().to_jsval(*cx, rval);
+                return;
             },
             constants::COPY_WRITE_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_copy_write_buffer.get()
-                );
+                self.bound_copy_write_buffer.get().to_jsval(*cx, rval);
+                return;
             },
             constants::PIXEL_PACK_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_pixel_pack_buffer.get()
-                );
+                self.bound_pixel_pack_buffer.get().to_jsval(*cx, rval);
+                return;
             },
             constants::PIXEL_UNPACK_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_pixel_unpack_buffer.get()
-                );
+                self.bound_pixel_unpack_buffer.get().to_jsval(*cx, rval);
+                return;
             },
             constants::TRANSFORM_FEEDBACK_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_transform_feedback_buffer.get()
-                );
+                self.bound_transform_feedback_buffer
+                    .get()
+                    .to_jsval(*cx, rval);
+                return;
             },
             constants::UNIFORM_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(*cx, &self.bound_uniform_buffer.get());
+                self.bound_uniform_buffer.get().to_jsval(*cx, rval);
+                return;
             },
             constants::TRANSFORM_FEEDBACK_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    self.current_transform_feedback.get()
-                );
+                self.current_transform_feedback.get().to_jsval(*cx, rval);
+                return;
             },
             constants::ELEMENT_ARRAY_BUFFER_BINDING => unsafe {
                 let buffer = self.current_vao().element_array_buffer().get();
-                return optional_root_object_to_js_or_null!(*cx, buffer);
+                buffer.to_jsval(*cx, rval);
+                return;
             },
             constants::VERTEX_ARRAY_BINDING => unsafe {
                 let vao = self.current_vao();
                 let vao = vao.id().map(|_| &*vao);
-                return optional_root_object_to_js_or_null!(*cx, vao);
+                vao.to_jsval(*cx, rval);
+                return;
             },
             // NOTE: DRAW_FRAMEBUFFER_BINDING is the same as FRAMEBUFFER_BINDING, handled on the WebGL1 side
             constants::READ_FRAMEBUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.base.get_read_framebuffer_slot().get()
-                );
+                self.base
+                    .get_read_framebuffer_slot()
+                    .get()
+                    .to_jsval(*cx, rval);
+                return;
             },
             constants::READ_BUFFER => {
                 let buffer = match self.base.get_read_framebuffer_slot().get() {
                     Some(fb) => fb.read_buffer(),
                     None => self.default_fb_readbuffer.get(),
                 };
-                return UInt32Value(buffer);
+                rval.set(UInt32Value(buffer));
+                return;
             },
             constants::DRAW_BUFFER0..=constants::DRAW_BUFFER15 => {
                 let buffer = match self.base.get_read_framebuffer_slot().get() {
@@ -1038,29 +1050,38 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
                     },
                     None => constants::NONE,
                 };
-                return UInt32Value(buffer);
+                rval.set(UInt32Value(buffer));
+                return;
             },
             constants::MAX_TEXTURE_LOD_BIAS => {
-                return DoubleValue(self.base.limits().max_texture_lod_bias as f64)
+                rval.set(DoubleValue(self.base.limits().max_texture_lod_bias as f64));
+                return;
             },
             constants::MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS => {
-                return DoubleValue(
+                rval.set(DoubleValue(
                     self.base.limits().max_combined_fragment_uniform_components as f64,
-                )
+                ));
+                return;
             },
             constants::MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS => {
-                return DoubleValue(
+                rval.set(DoubleValue(
                     self.base.limits().max_combined_vertex_uniform_components as f64,
-                )
+                ));
+                return;
             },
             constants::MAX_ELEMENT_INDEX => {
-                return DoubleValue(self.base.limits().max_element_index as f64)
+                rval.set(DoubleValue(self.base.limits().max_element_index as f64));
+                return;
             },
             constants::MAX_UNIFORM_BLOCK_SIZE => {
-                return DoubleValue(self.base.limits().max_uniform_block_size as f64)
+                rval.set(DoubleValue(
+                    self.base.limits().max_uniform_block_size as f64,
+                ));
+                return;
             },
             constants::MIN_PROGRAM_TEXEL_OFFSET => {
-                return Int32Value(self.base.limits().min_program_texel_offset)
+                rval.set(Int32Value(self.base.limits().min_program_texel_offset));
+                return;
             },
             _ => {},
         }
@@ -1109,15 +1130,16 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
             _ => None,
         };
         if let Some(limit) = limit {
-            return UInt32Value(limit);
+            rval.set(UInt32Value(limit));
+            return;
         }
 
-        self.base.GetParameter(cx, parameter)
+        self.base.GetParameter(cx, parameter, rval)
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8>
-    fn GetTexParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal {
-        self.base.GetTexParameter(cx, target, pname)
+    fn GetTexParameter(&self, cx: JSContext, target: u32, pname: u32, retval: MutableHandleValue) {
+        self.base.GetTexParameter(cx, target, pname, retval)
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
@@ -1152,7 +1174,8 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
         target: u32,
         attachment: u32,
         pname: u32,
-    ) -> JSVal {
+        mut rval: MutableHandleValue,
+    ) {
         let fb_slot = match target {
             constants::FRAMEBUFFER | constants::DRAW_FRAMEBUFFER => {
                 self.base.get_draw_framebuffer_slot()
@@ -1160,31 +1183,43 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
             constants::READ_FRAMEBUFFER => self.base.get_read_framebuffer_slot(),
             _ => {
                 self.base.webgl_error(InvalidEnum);
-                return NullValue();
+                rval.set(NullValue());
+                return;
             },
         };
 
         if let Some(fb) = fb_slot.get() {
             // A selected framebuffer is bound to the target
-            handle_potential_webgl_error!(self.base, fb.validate_transparent(), return NullValue());
             handle_potential_webgl_error!(
                 self.base,
-                self.get_specific_fb_attachment_param(cx, &fb, target, attachment, pname),
-                NullValue()
+                fb.validate_transparent(),
+                return rval.set(NullValue())
+            );
+            handle_potential_webgl_error!(
+                self.base,
+                self.get_specific_fb_attachment_param(cx, &fb, target, attachment, pname, rval),
+                rval.set(NullValue())
             )
         } else {
             // The default framebuffer is bound to the target
             handle_potential_webgl_error!(
                 self.base,
-                self.get_default_fb_attachment_param(attachment, pname),
-                NullValue()
+                self.get_default_fb_attachment_param(attachment, pname, rval),
+                rval.set(NullValue())
             )
         }
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7>
-    fn GetRenderbufferParameter(&self, cx: JSContext, target: u32, pname: u32) -> JSVal {
-        self.base.GetRenderbufferParameter(cx, target, pname)
+    fn GetRenderbufferParameter(
+        &self,
+        cx: JSContext,
+        target: u32,
+        pname: u32,
+        retval: MutableHandleValue,
+    ) {
+        self.base
+            .GetRenderbufferParameter(cx, target, pname, retval)
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3>
@@ -1856,24 +1891,30 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
-    fn GetProgramParameter(&self, cx: JSContext, program: &WebGLProgram, param_id: u32) -> JSVal {
+    fn GetProgramParameter(
+        &self,
+        cx: JSContext,
+        program: &WebGLProgram,
+        param_id: u32,
+        mut retval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(program),
-            return NullValue()
+            return retval.set(NullValue())
         );
         if program.is_deleted() {
             self.base.webgl_error(InvalidOperation);
-            return NullValue();
+            return retval.set(NullValue());
         }
         match param_id {
             constants::TRANSFORM_FEEDBACK_VARYINGS => {
-                Int32Value(program.transform_feedback_varyings_length())
+                retval.set(Int32Value(program.transform_feedback_varyings_length()))
             },
             constants::TRANSFORM_FEEDBACK_BUFFER_MODE => {
-                Int32Value(program.transform_feedback_buffer_mode())
+                retval.set(Int32Value(program.transform_feedback_buffer_mode()))
             },
-            _ => self.base.GetProgramParameter(cx, program, param_id),
+            _ => self.base.GetProgramParameter(cx, program, param_id, retval),
         }
     }
 
@@ -1883,8 +1924,14 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
-    fn GetShaderParameter(&self, cx: JSContext, shader: &WebGLShader, param_id: u32) -> JSVal {
-        self.base.GetShaderParameter(cx, shader, param_id)
+    fn GetShaderParameter(
+        &self,
+        cx: JSContext,
+        shader: &WebGLShader,
+        param_id: u32,
+        retval: MutableHandleValue,
+    ) {
+        self.base.GetShaderParameter(cx, shader, param_id, retval)
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
@@ -1899,7 +1946,13 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.2>
     #[allow(unsafe_code)]
-    fn GetIndexedParameter(&self, cx: JSContext, target: u32, index: u32) -> JSVal {
+    fn GetIndexedParameter(
+        &self,
+        cx: JSContext,
+        target: u32,
+        index: u32,
+        mut retval: MutableHandleValue,
+    ) {
         let bindings = match target {
             constants::TRANSFORM_FEEDBACK_BUFFER_BINDING |
             constants::TRANSFORM_FEEDBACK_BUFFER_SIZE |
@@ -1911,7 +1964,7 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
             constants::UNIFORM_BUFFER_START => &self.indexed_uniform_buffer_bindings,
             _ => {
                 self.base.webgl_error(InvalidEnum);
-                return NullValue();
+                return retval.set(NullValue());
             },
         };
 
@@ -1919,19 +1972,19 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
             Some(binding) => binding,
             None => {
                 self.base.webgl_error(InvalidValue);
-                return NullValue();
+                return retval.set(NullValue());
             },
         };
 
         match target {
             constants::TRANSFORM_FEEDBACK_BUFFER_BINDING | constants::UNIFORM_BUFFER_BINDING => unsafe {
-                optional_root_object_to_js_or_null!(*cx, binding.buffer.get())
+                binding.buffer.get().to_jsval(*cx, retval)
             },
             constants::TRANSFORM_FEEDBACK_BUFFER_START | constants::UNIFORM_BUFFER_START => {
-                Int32Value(binding.start.get() as _)
+                retval.set(Int32Value(binding.start.get() as _))
             },
             constants::TRANSFORM_FEEDBACK_BUFFER_SIZE | constants::UNIFORM_BUFFER_SIZE => {
-                Int32Value(binding.size.get() as _)
+                retval.set(Int32Value(binding.size.get() as _))
             },
             _ => unreachable!(),
         }
@@ -1947,8 +2000,8 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9>
-    fn GetVertexAttrib(&self, cx: JSContext, index: u32, pname: u32) -> JSVal {
-        self.base.GetVertexAttrib(cx, index, pname)
+    fn GetVertexAttrib(&self, cx: JSContext, index: u32, pname: u32, retval: MutableHandleValue) {
+        self.base.GetVertexAttrib(cx, index, pname, retval)
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.10>
@@ -2709,68 +2762,88 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
         cx: JSContext,
         program: &WebGLProgram,
         location: &WebGLUniformLocation,
-    ) -> JSVal {
+        mut retval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self.base,
             self.base.uniform_check_program(program, location),
-            return NullValue()
+            return retval.set(NullValue())
         );
 
         let triple = (&*self.base, program.id(), location.id());
 
         match location.type_() {
-            constants::UNSIGNED_INT => {
-                UInt32Value(uniform_get(triple, WebGLCommand::GetUniformUint))
-            },
+            constants::UNSIGNED_INT => retval.set(UInt32Value(uniform_get(
+                triple,
+                WebGLCommand::GetUniformUint,
+            ))),
             constants::UNSIGNED_INT_VEC2 => unsafe {
-                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint2))
+                uniform_typed::<Uint32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformUint2),
+                    retval,
+                )
             },
             constants::UNSIGNED_INT_VEC3 => unsafe {
-                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint3))
+                uniform_typed::<Uint32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformUint3),
+                    retval,
+                )
             },
             constants::UNSIGNED_INT_VEC4 => unsafe {
-                uniform_typed::<Uint32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformUint4))
+                uniform_typed::<Uint32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformUint4),
+                    retval,
+                )
             },
             constants::FLOAT_MAT2x3 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat2x3),
+                    retval,
                 )
             },
             constants::FLOAT_MAT2x4 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat2x4),
+                    retval,
                 )
             },
             constants::FLOAT_MAT3x2 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat3x2),
+                    retval,
                 )
             },
             constants::FLOAT_MAT3x4 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat3x4),
+                    retval,
                 )
             },
             constants::FLOAT_MAT4x2 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat4x2),
+                    retval,
                 )
             },
             constants::FLOAT_MAT4x3 => unsafe {
                 uniform_typed::<Float32>(
                     *cx,
                     &uniform_get(triple, WebGLCommand::GetUniformFloat4x3),
+                    retval,
                 )
             },
             constants::SAMPLER_3D | constants::SAMPLER_2D_ARRAY => {
-                Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt))
+                retval.set(Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt)))
             },
-            _ => self.base.GetUniform(cx, program, location),
+            _ => self.base.GetUniform(cx, program, location, retval),
         }
     }
 
@@ -3530,21 +3603,21 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.12>
     #[rustfmt::skip]
-    fn GetQueryParameter(&self, _cx: JSContext, query: &WebGLQuery, pname: u32) -> JSVal {
+    fn GetQueryParameter(&self, _cx: JSContext, query: &WebGLQuery, pname: u32, mut retval: MutableHandleValue) {
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(query),
-            return NullValue()
+            return retval.set(NullValue())
         );
         match query.get_parameter(&self.base, pname) {
             Ok(value) => match pname {
-                constants::QUERY_RESULT => UInt32Value(value),
-                constants::QUERY_RESULT_AVAILABLE => BooleanValue(value != 0),
+                constants::QUERY_RESULT => retval.set(UInt32Value(value)),
+                constants::QUERY_RESULT_AVAILABLE => retval.set(BooleanValue(value != 0)),
                 _ => unreachable!(),
             },
             Err(error) => {
                 self.base.webgl_error(error);
-                NullValue()
+                retval.set(NullValue())
             },
         }
     }
@@ -3631,30 +3704,36 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.14>
-    fn GetSyncParameter(&self, _cx: JSContext, sync: &WebGLSync, pname: u32) -> JSVal {
+    fn GetSyncParameter(
+        &self,
+        _cx: JSContext,
+        sync: &WebGLSync,
+        pname: u32,
+        mut retval: MutableHandleValue,
+    ) {
         if !sync.is_valid() {
             self.base.webgl_error(InvalidOperation);
-            return NullValue();
+            return retval.set(NullValue());
         }
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(sync),
-            return NullValue()
+            return retval.set(NullValue())
         );
         match pname {
             constants::OBJECT_TYPE | constants::SYNC_CONDITION | constants::SYNC_FLAGS => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.base
                     .send_command(WebGLCommand::GetSyncParameter(sync.id(), pname, sender));
-                UInt32Value(receiver.recv().unwrap())
+                retval.set(UInt32Value(receiver.recv().unwrap()))
             },
             constants::SYNC_STATUS => match sync.get_sync_status(pname, &self.base) {
-                Some(status) => UInt32Value(status),
-                None => UInt32Value(constants::UNSIGNALED),
+                Some(status) => retval.set(UInt32Value(status)),
+                None => retval.set(UInt32Value(constants::UNSIGNALED)),
             },
             _ => {
                 self.base.webgl_error(InvalidEnum);
-                NullValue()
+                retval.set(NullValue())
             },
         }
     }
@@ -3711,20 +3790,26 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.13>
-    fn GetSamplerParameter(&self, _cx: JSContext, sampler: &WebGLSampler, pname: u32) -> JSVal {
+    fn GetSamplerParameter(
+        &self,
+        _cx: JSContext,
+        sampler: &WebGLSampler,
+        pname: u32,
+        mut retval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(sampler),
-            return NullValue()
+            return retval.set(NullValue())
         );
         match sampler.get_parameter(&self.base, pname) {
             Ok(value) => match value {
-                WebGLSamplerValue::GLenum(value) => UInt32Value(value),
-                WebGLSamplerValue::Float(value) => DoubleValue(value as f64),
+                WebGLSamplerValue::GLenum(value) => retval.set(UInt32Value(value)),
+                WebGLSamplerValue::Float(value) => retval.set(DoubleValue(value as f64)),
             },
             Err(error) => {
                 self.base.webgl_error(error);
-                NullValue()
+                retval.set(NullValue())
             },
         }
     }
@@ -4085,19 +4170,19 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
         program: &WebGLProgram,
         indices: Vec<u32>,
         pname: u32,
-    ) -> JSVal {
+        mut rval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(program),
-            return NullValue()
+            return rval.set(NullValue())
         );
         let values = handle_potential_webgl_error!(
             self.base,
             program.get_active_uniforms(indices, pname),
-            return NullValue()
+            return rval.set(NullValue())
         );
 
-        rooted!(in(*cx) let mut rval = UndefinedValue());
         match pname {
             constants::UNIFORM_SIZE |
             constants::UNIFORM_TYPE |
@@ -4105,15 +4190,14 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
             constants::UNIFORM_OFFSET |
             constants::UNIFORM_ARRAY_STRIDE |
             constants::UNIFORM_MATRIX_STRIDE => unsafe {
-                values.to_jsval(*cx, rval.handle_mut());
+                values.to_jsval(*cx, rval);
             },
             constants::UNIFORM_IS_ROW_MAJOR => unsafe {
                 let values = values.iter().map(|&v| v != 0).collect::<Vec<_>>();
-                values.to_jsval(*cx, rval.handle_mut());
+                values.to_jsval(*cx, rval);
             },
             _ => unreachable!(),
         }
-        rval.get()
     }
 
     /// <https://www.khronos.org/registry/webgl/specs/latest/2.0/#3.7.16>
@@ -4138,34 +4222,35 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
         program: &WebGLProgram,
         block_index: u32,
         pname: u32,
-    ) -> JSVal {
+        mut retval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self.base,
             self.base.validate_ownership(program),
-            return NullValue()
+            return retval.set(NullValue())
         );
         let values = handle_potential_webgl_error!(
             self.base,
             program.get_active_uniform_block_parameter(block_index, pname),
-            return NullValue()
+            return retval.set(NullValue())
         );
         match pname {
             constants::UNIFORM_BLOCK_BINDING |
             constants::UNIFORM_BLOCK_DATA_SIZE |
             constants::UNIFORM_BLOCK_ACTIVE_UNIFORMS => {
                 assert!(values.len() == 1);
-                UInt32Value(values[0] as u32)
+                retval.set(UInt32Value(values[0] as u32))
             },
             constants::UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES => unsafe {
                 let values = values.iter().map(|&v| v as u32).collect::<Vec<_>>();
                 rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>());
                 Uint32Array::create(*cx, CreateWith::Slice(&values), result.handle_mut()).unwrap();
-                ObjectValue(result.get())
+                retval.set(ObjectValue(result.get()))
             },
             constants::UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER |
             constants::UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER => {
                 assert!(values.len() == 1);
-                BooleanValue(values[0] != 0)
+                retval.set(BooleanValue(values[0] != 0))
             },
             _ => unreachable!(),
         }
@@ -4367,16 +4452,17 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
         target: u32,
         internal_format: u32,
         pname: u32,
-    ) -> JSVal {
+        mut retval: MutableHandleValue,
+    ) {
         if target != constants::RENDERBUFFER {
             self.base.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         match handle_potential_webgl_error!(
             self.base,
             InternalFormatParameter::from_u32(pname),
-            return NullValue()
+            return retval.set(NullValue())
         ) {
             InternalFormatParameter::IntVec(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
@@ -4395,7 +4481,7 @@ impl WebGL2RenderingContextMethods for WebGL2RenderingContext {
                     rval.handle_mut(),
                 )
                 .unwrap();
-                ObjectValue(rval.get())
+                retval.set(ObjectValue(rval.get()))
             },
         }
     }
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs
index d662b52254d24..7751bce87ce46 100644
--- a/components/script/dom/webglrenderingcontext.rs
+++ b/components/script/dom/webglrenderingcontext.rs
@@ -22,11 +22,8 @@ use dom_struct::dom_struct;
 use euclid::default::{Point2D, Rect, Size2D};
 use ipc_channel::ipc::{self, IpcSharedMemory};
 use js::jsapi::{JSContext, JSObject, Type};
-use js::jsval::{
-    BooleanValue, DoubleValue, Int32Value, JSVal, NullValue, ObjectValue, UInt32Value,
-    UndefinedValue,
-};
-use js::rust::CustomAutoRooterGuard;
+use js::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, ObjectValue, UInt32Value};
+use js::rust::{CustomAutoRooterGuard, MutableHandleValue};
 use js::typedarray::{
     ArrayBufferView, CreateWith, Float32, Float32Array, Int32, Int32Array, TypedArray,
     TypedArrayElementCreator, Uint32Array,
@@ -131,14 +128,17 @@ where
 }
 
 #[allow(unsafe_code)]
-pub unsafe fn uniform_typed<T>(cx: *mut JSContext, value: &[T::Element]) -> JSVal
-where
+pub unsafe fn uniform_typed<T>(
+    cx: *mut JSContext,
+    value: &[T::Element],
+    mut retval: MutableHandleValue,
+) where
     T: TypedArrayElementCreator,
 {
     rooted!(in(cx) let mut rval = ptr::null_mut::<JSObject>());
     <TypedArray<T, *mut JSObject>>::create(cx, CreateWith::Slice(value), rval.handle_mut())
         .unwrap();
-    ObjectValue(rval.get())
+    retval.set(ObjectValue(rval.get()));
 }
 
 /// Set of bitflags for texture unpacking (texImage2d, etc...)
@@ -1946,18 +1946,26 @@ impl WebGLRenderingContext {
         });
     }
 
-    pub fn get_buffer_param(&self, buffer: Option<DomRoot<WebGLBuffer>>, parameter: u32) -> JSVal {
-        let buffer =
-            handle_potential_webgl_error!(self, buffer.ok_or(InvalidOperation), return NullValue());
+    pub fn get_buffer_param(
+        &self,
+        buffer: Option<DomRoot<WebGLBuffer>>,
+        parameter: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        let buffer = handle_potential_webgl_error!(
+            self,
+            buffer.ok_or(InvalidOperation),
+            return retval.set(NullValue())
+        );
 
-        match parameter {
+        retval.set(match parameter {
             constants::BUFFER_SIZE => Int32Value(buffer.capacity() as i32),
             constants::BUFFER_USAGE => Int32Value(buffer.usage() as i32),
             _ => {
                 self.webgl_error(InvalidEnum);
                 NullValue()
             },
-        }
+        })
     }
 }
 
@@ -2019,42 +2027,53 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.5
-    fn GetBufferParameter(&self, _cx: SafeJSContext, target: u32, parameter: u32) -> JSVal {
-        let buffer =
-            handle_potential_webgl_error!(self, self.bound_buffer(target), return NullValue());
-        self.get_buffer_param(buffer, parameter)
+    fn GetBufferParameter(
+        &self,
+        _cx: SafeJSContext,
+        target: u32,
+        parameter: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        let buffer = handle_potential_webgl_error!(
+            self,
+            self.bound_buffer(target),
+            return retval.set(NullValue())
+        );
+        self.get_buffer_param(buffer, parameter, retval)
     }
 
     #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.3
-    fn GetParameter(&self, cx: SafeJSContext, parameter: u32) -> JSVal {
+    fn GetParameter(&self, cx: SafeJSContext, parameter: u32, mut retval: MutableHandleValue) {
         if !self
             .extension_manager
             .is_get_parameter_name_enabled(parameter)
         {
             self.webgl_error(WebGLError::InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         match parameter {
             constants::ARRAY_BUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(*cx, &self.bound_buffer_array.get());
+                self.bound_buffer_array.get().to_jsval(*cx, retval);
+                return;
             },
             constants::CURRENT_PROGRAM => unsafe {
-                return optional_root_object_to_js_or_null!(*cx, &self.current_program.get());
+                self.current_program.get().to_jsval(*cx, retval);
+                return;
             },
             constants::ELEMENT_ARRAY_BUFFER_BINDING => unsafe {
                 let buffer = self.current_vao().element_array_buffer().get();
-                return optional_root_object_to_js_or_null!(*cx, buffer);
+                buffer.to_jsval(*cx, retval);
+                return;
             },
             constants::FRAMEBUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(
-                    *cx,
-                    &self.bound_draw_framebuffer.get()
-                );
+                self.bound_draw_framebuffer.get().to_jsval(*cx, retval);
+                return;
             },
             constants::RENDERBUFFER_BINDING => unsafe {
-                return optional_root_object_to_js_or_null!(*cx, &self.bound_renderbuffer.get());
+                self.bound_renderbuffer.get().to_jsval(*cx, retval);
+                return;
             },
             constants::TEXTURE_BINDING_2D => unsafe {
                 let texture = self
@@ -2062,7 +2081,8 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     .active_texture_slot(constants::TEXTURE_2D, self.webgl_version())
                     .unwrap()
                     .get();
-                return optional_root_object_to_js_or_null!(*cx, texture);
+                texture.to_jsval(*cx, retval);
+                return;
             },
             constants::TEXTURE_BINDING_CUBE_MAP => unsafe {
                 let texture = self
@@ -2070,11 +2090,13 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     .active_texture_slot(constants::TEXTURE_CUBE_MAP, self.webgl_version())
                     .unwrap()
                     .get();
-                return optional_root_object_to_js_or_null!(*cx, texture);
+                texture.to_jsval(*cx, retval);
+                return;
             },
             OESVertexArrayObjectConstants::VERTEX_ARRAY_BINDING_OES => unsafe {
                 let vao = self.current_vao.get().filter(|vao| vao.id().is_some());
-                return optional_root_object_to_js_or_null!(*cx, vao);
+                vao.to_jsval(*cx, retval);
+                return;
             },
             // In readPixels we currently support RGBA/UBYTE only.  If
             // we wanted to support other formats, we could ask the
@@ -2084,16 +2106,16 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             constants::IMPLEMENTATION_COLOR_READ_FORMAT => {
                 if self.validate_framebuffer().is_err() {
                     self.webgl_error(InvalidOperation);
-                    return NullValue();
+                    return retval.set(NullValue());
                 }
-                return Int32Value(constants::RGBA as i32);
+                return retval.set(Int32Value(constants::RGBA as i32));
             },
             constants::IMPLEMENTATION_COLOR_READ_TYPE => {
                 if self.validate_framebuffer().is_err() {
                     self.webgl_error(InvalidOperation);
-                    return NullValue();
+                    return retval.set(NullValue());
                 }
-                return Int32Value(constants::UNSIGNED_BYTE as i32);
+                return retval.set(Int32Value(constants::UNSIGNED_BYTE as i32));
             },
             constants::COMPRESSED_TEXTURE_FORMATS => unsafe {
                 let format_ids = self.extension_manager.get_tex_compression_ids();
@@ -2101,44 +2123,50 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 rooted!(in(*cx) let mut rval = ptr::null_mut::<JSObject>());
                 Uint32Array::create(*cx, CreateWith::Slice(&format_ids), rval.handle_mut())
                     .unwrap();
-                return ObjectValue(rval.get());
+                return retval.set(ObjectValue(rval.get()));
             },
             constants::VERSION => unsafe {
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                "WebGL 1.0".to_jsval(*cx, rval.handle_mut());
-                return rval.get();
+                "WebGL 1.0".to_jsval(*cx, retval);
+                return;
             },
             constants::RENDERER | constants::VENDOR => unsafe {
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                "Mozilla/Servo".to_jsval(*cx, rval.handle_mut());
-                return rval.get();
+                "Mozilla/Servo".to_jsval(*cx, retval);
+                return;
             },
             constants::SHADING_LANGUAGE_VERSION => unsafe {
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                "WebGL GLSL ES 1.0".to_jsval(*cx, rval.handle_mut());
-                return rval.get();
+                "WebGL GLSL ES 1.0".to_jsval(*cx, retval);
+                return;
             },
             constants::UNPACK_FLIP_Y_WEBGL => {
                 let unpack = self.texture_unpacking_settings.get();
-                return BooleanValue(unpack.contains(TextureUnpacking::FLIP_Y_AXIS));
+                retval.set(BooleanValue(unpack.contains(TextureUnpacking::FLIP_Y_AXIS)));
+                return;
             },
             constants::UNPACK_PREMULTIPLY_ALPHA_WEBGL => {
                 let unpack = self.texture_unpacking_settings.get();
-                return BooleanValue(unpack.contains(TextureUnpacking::PREMULTIPLY_ALPHA));
+                retval.set(BooleanValue(
+                    unpack.contains(TextureUnpacking::PREMULTIPLY_ALPHA),
+                ));
+                return;
             },
             constants::PACK_ALIGNMENT => {
-                return UInt32Value(self.texture_packing_alignment.get() as u32);
+                retval.set(UInt32Value(self.texture_packing_alignment.get() as u32));
+                return;
             },
             constants::UNPACK_ALIGNMENT => {
-                return UInt32Value(self.texture_unpacking_alignment.get());
+                retval.set(UInt32Value(self.texture_unpacking_alignment.get()));
+                return;
             },
             constants::UNPACK_COLORSPACE_CONVERSION_WEBGL => {
                 let unpack = self.texture_unpacking_settings.get();
-                return UInt32Value(if unpack.contains(TextureUnpacking::CONVERT_COLORSPACE) {
-                    constants::BROWSER_DEFAULT_WEBGL
-                } else {
-                    constants::NONE
-                });
+                retval.set(UInt32Value(
+                    if unpack.contains(TextureUnpacking::CONVERT_COLORSPACE) {
+                        constants::BROWSER_DEFAULT_WEBGL
+                    } else {
+                        constants::NONE
+                    },
+                ));
+                return;
             },
             _ => {},
         }
@@ -2165,34 +2193,34 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             _ => None,
         };
         if let Some(limit) = limit {
-            return UInt32Value(limit);
+            retval.set(UInt32Value(limit));
+            return;
         }
 
         if let Ok(value) = self.capabilities.is_enabled(parameter) {
-            return BooleanValue(value);
+            retval.set(BooleanValue(value));
+            return;
         }
 
         match handle_potential_webgl_error!(
             self,
             Parameter::from_u32(parameter),
-            return NullValue()
+            return retval.set(NullValue())
         ) {
             Parameter::Bool(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetParameterBool(param, sender));
-                BooleanValue(receiver.recv().unwrap())
+                retval.set(BooleanValue(receiver.recv().unwrap()))
             },
             Parameter::Bool4(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetParameterBool4(param, sender));
-                rooted!(in(*cx) let mut rval = UndefinedValue());
-                receiver.recv().unwrap().to_jsval(*cx, rval.handle_mut());
-                rval.get()
+                receiver.recv().unwrap().to_jsval(*cx, retval);
             },
             Parameter::Int(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetParameterInt(param, sender));
-                Int32Value(receiver.recv().unwrap())
+                retval.set(Int32Value(receiver.recv().unwrap()))
             },
             Parameter::Int2(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
@@ -2204,7 +2232,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     rval.handle_mut(),
                 )
                 .unwrap();
-                ObjectValue(rval.get())
+                retval.set(ObjectValue(rval.get()))
             },
             Parameter::Int4(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
@@ -2216,12 +2244,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     rval.handle_mut(),
                 )
                 .unwrap();
-                ObjectValue(rval.get())
+                retval.set(ObjectValue(rval.get()))
             },
             Parameter::Float(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetParameterFloat(param, sender));
-                DoubleValue(receiver.recv().unwrap() as f64)
+                retval.set(DoubleValue(receiver.recv().unwrap() as f64))
             },
             Parameter::Float2(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
@@ -2233,7 +2261,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     rval.handle_mut(),
                 )
                 .unwrap();
-                ObjectValue(rval.get())
+                retval.set(ObjectValue(rval.get()))
             },
             Parameter::Float4(param) => unsafe {
                 let (sender, receiver) = webgl_channel().unwrap();
@@ -2245,23 +2273,29 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                     rval.handle_mut(),
                 )
                 .unwrap();
-                ObjectValue(rval.get())
+                retval.set(ObjectValue(rval.get()))
             },
         }
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.8
-    fn GetTexParameter(&self, _cx: SafeJSContext, target: u32, pname: u32) -> JSVal {
+    fn GetTexParameter(
+        &self,
+        _cx: SafeJSContext,
+        target: u32,
+        pname: u32,
+        mut retval: MutableHandleValue,
+    ) {
         let texture_slot = handle_potential_webgl_error!(
             self,
             self.textures
                 .active_texture_slot(target, self.webgl_version()),
-            return NullValue()
+            return retval.set(NullValue())
         );
         let texture = handle_potential_webgl_error!(
             self,
             texture_slot.get().ok_or(InvalidOperation),
-            return NullValue()
+            return retval.set(NullValue())
         );
 
         if !self
@@ -2269,45 +2303,49 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             .is_get_tex_parameter_name_enabled(pname)
         {
             self.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         match pname {
-            constants::TEXTURE_MAG_FILTER => return UInt32Value(texture.mag_filter()),
-            constants::TEXTURE_MIN_FILTER => return UInt32Value(texture.min_filter()),
+            constants::TEXTURE_MAG_FILTER => return retval.set(UInt32Value(texture.mag_filter())),
+            constants::TEXTURE_MIN_FILTER => return retval.set(UInt32Value(texture.min_filter())),
             _ => {},
         }
 
-        let texparam =
-            handle_potential_webgl_error!(self, TexParameter::from_u32(pname), return NullValue());
+        let texparam = handle_potential_webgl_error!(
+            self,
+            TexParameter::from_u32(pname),
+            return retval.set(NullValue())
+        );
         if self.webgl_version() < texparam.required_webgl_version() {
             self.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         if let Some(value) = texture.maybe_get_tex_parameter(texparam) {
             match value {
-                TexParameterValue::Float(v) => return DoubleValue(v as f64),
-                TexParameterValue::Int(v) => return Int32Value(v),
-                TexParameterValue::Bool(v) => return BooleanValue(v),
+                TexParameterValue::Float(v) => retval.set(DoubleValue(v as f64)),
+                TexParameterValue::Int(v) => retval.set(Int32Value(v)),
+                TexParameterValue::Bool(v) => retval.set(BooleanValue(v)),
             }
+            return;
         }
 
         match texparam {
             TexParameter::Float(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetTexParameterFloat(target, param, sender));
-                DoubleValue(receiver.recv().unwrap() as f64)
+                retval.set(DoubleValue(receiver.recv().unwrap() as f64))
             },
             TexParameter::Int(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetTexParameterInt(target, param, sender));
-                Int32Value(receiver.recv().unwrap())
+                retval.set(Int32Value(receiver.recv().unwrap()))
             },
             TexParameter::Bool(param) => {
                 let (sender, receiver) = webgl_channel().unwrap();
                 self.send_command(WebGLCommand::GetTexParameterBool(target, param, sender));
-                BooleanValue(receiver.recv().unwrap())
+                retval.set(BooleanValue(receiver.recv().unwrap()))
             },
         }
     }
@@ -3123,15 +3161,20 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
         target: u32,
         attachment: u32,
         pname: u32,
-    ) -> JSVal {
+        mut retval: MutableHandleValue,
+    ) {
         // Check if currently bound framebuffer is non-zero as per spec.
         if let Some(fb) = self.bound_draw_framebuffer.get() {
             // Opaque framebuffers cannot have their attachments inspected
             // https://immersive-web.github.io/webxr/#opaque-framebuffer
-            handle_potential_webgl_error!(self, fb.validate_transparent(), return NullValue());
+            handle_potential_webgl_error!(
+                self,
+                fb.validate_transparent(),
+                return retval.set(NullValue())
+            );
         } else {
             self.webgl_error(InvalidOperation);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         // Note: commented out stuff is for the WebGL2 standard.
@@ -3192,7 +3235,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
 
         if !target_matches || !attachment_matches || !pname_matches || !bound_attachment_matches {
             self.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         // From the GLES2 spec:
@@ -3208,19 +3251,17 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             if let Some(webgl_attachment) = fb.attachment(attachment) {
                 match webgl_attachment {
                     WebGLFramebufferAttachmentRoot::Renderbuffer(rb) => unsafe {
-                        rooted!(in(*cx) let mut rval = NullValue());
-                        rb.to_jsval(*cx, rval.handle_mut());
-                        return rval.get();
+                        rb.to_jsval(*cx, retval);
+                        return;
                     },
                     WebGLFramebufferAttachmentRoot::Texture(texture) => unsafe {
-                        rooted!(in(*cx) let mut rval = NullValue());
-                        texture.to_jsval(*cx, rval.handle_mut());
-                        return rval.get();
+                        texture.to_jsval(*cx, retval);
+                        return;
                     },
                 }
             }
             self.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         let (sender, receiver) = webgl_channel().unwrap();
@@ -3228,11 +3269,17 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             target, attachment, pname, sender,
         ));
 
-        Int32Value(receiver.recv().unwrap())
+        retval.set(Int32Value(receiver.recv().unwrap()))
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.7
-    fn GetRenderbufferParameter(&self, _cx: SafeJSContext, target: u32, pname: u32) -> JSVal {
+    fn GetRenderbufferParameter(
+        &self,
+        _cx: SafeJSContext,
+        target: u32,
+        pname: u32,
+        mut retval: MutableHandleValue,
+    ) {
         // We do not check to see if the renderbuffer came from an opaque framebuffer
         // https://github.com/immersive-web/webxr/issues/862
         let target_matches = target == constants::RENDERBUFFER;
@@ -3252,12 +3299,12 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
 
         if !target_matches || !pname_matches {
             self.webgl_error(InvalidEnum);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         if self.bound_renderbuffer.get().is_none() {
             self.webgl_error(InvalidOperation);
-            return NullValue();
+            return retval.set(NullValue());
         }
 
         let result = if pname == constants::RENDERBUFFER_INTERNAL_FORMAT {
@@ -3271,7 +3318,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
             receiver.recv().unwrap()
         };
 
-        Int32Value(result)
+        retval.set(Int32Value(result))
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
@@ -3287,13 +3334,23 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
-    fn GetProgramParameter(&self, _: SafeJSContext, program: &WebGLProgram, param: u32) -> JSVal {
-        handle_potential_webgl_error!(self, self.validate_ownership(program), return NullValue());
+    fn GetProgramParameter(
+        &self,
+        _: SafeJSContext,
+        program: &WebGLProgram,
+        param: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        handle_potential_webgl_error!(
+            self,
+            self.validate_ownership(program),
+            return retval.set(NullValue())
+        );
         if program.is_deleted() {
             self.webgl_error(InvalidOperation);
-            return NullValue();
+            return retval.set(NullValue());
         }
-        match param {
+        retval.set(match param {
             constants::DELETE_STATUS => BooleanValue(program.is_marked_for_deletion()),
             constants::LINK_STATUS => BooleanValue(program.is_linked()),
             constants::VALIDATE_STATUS => {
@@ -3318,7 +3375,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 self.webgl_error(InvalidEnum);
                 NullValue()
             },
-        }
+        })
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
@@ -3328,13 +3385,23 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
-    fn GetShaderParameter(&self, _: SafeJSContext, shader: &WebGLShader, param: u32) -> JSVal {
-        handle_potential_webgl_error!(self, self.validate_ownership(shader), return NullValue());
+    fn GetShaderParameter(
+        &self,
+        _: SafeJSContext,
+        shader: &WebGLShader,
+        param: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        handle_potential_webgl_error!(
+            self,
+            self.validate_ownership(shader),
+            return retval.set(NullValue())
+        );
         if shader.is_deleted() {
             self.webgl_error(InvalidValue);
-            return NullValue();
+            return retval.set(NullValue());
         }
-        match param {
+        retval.set(match param {
             constants::DELETE_STATUS => BooleanValue(shader.is_marked_for_deletion()),
             constants::COMPILE_STATUS => BooleanValue(shader.successfully_compiled()),
             constants::SHADER_TYPE => UInt32Value(shader.gl_type()),
@@ -3342,7 +3409,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 self.webgl_error(InvalidEnum);
                 NullValue()
             },
-        }
+        })
     }
 
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
@@ -3400,8 +3467,14 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
 
     #[allow(unsafe_code)]
     // https://www.khronos.org/registry/webgl/specs/latest/1.0/#5.14.9
-    fn GetVertexAttrib(&self, cx: SafeJSContext, index: u32, param: u32) -> JSVal {
-        let get_attrib = |data: Ref<VertexAttribData>| -> JSVal {
+    fn GetVertexAttrib(
+        &self,
+        cx: SafeJSContext,
+        index: u32,
+        param: u32,
+        mut retval: MutableHandleValue,
+    ) {
+        let mut get_attrib = |data: Ref<VertexAttribData>| {
             if param == constants::CURRENT_VERTEX_ATTRIB {
                 let attrib = self.current_vertex_attribs.borrow()[index as usize];
                 match attrib {
@@ -3415,7 +3488,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                                 result.handle_mut(),
                             )
                             .unwrap();
-                            return ObjectValue(result.get());
+                            return retval.set(ObjectValue(result.get()));
                         }
                     },
                     VertexAttrib::Int(x, y, z, w) => {
@@ -3424,7 +3497,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                             rooted!(in(*cx) let mut result = ptr::null_mut::<JSObject>());
                             Int32Array::create(*cx, CreateWith::Slice(&value), result.handle_mut())
                                 .unwrap();
-                            return ObjectValue(result.get());
+                            return retval.set(ObjectValue(result.get()));
                         }
                     },
                     VertexAttrib::Uint(x, y, z, w) => {
@@ -3437,7 +3510,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                                 result.handle_mut(),
                             )
                             .unwrap();
-                            return ObjectValue(result.get());
+                            return retval.set(ObjectValue(result.get()));
                         }
                     },
                 };
@@ -3447,28 +3520,32 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 .is_get_vertex_attrib_name_enabled(param)
             {
                 self.webgl_error(WebGLError::InvalidEnum);
-                return NullValue();
+                return retval.set(NullValue());
             }
 
             match param {
-                constants::VERTEX_ATTRIB_ARRAY_ENABLED => BooleanValue(data.enabled_as_array),
-                constants::VERTEX_ATTRIB_ARRAY_SIZE => Int32Value(data.size as i32),
-                constants::VERTEX_ATTRIB_ARRAY_TYPE => Int32Value(data.type_ as i32),
-                constants::VERTEX_ATTRIB_ARRAY_NORMALIZED => BooleanValue(data.normalized),
-                constants::VERTEX_ATTRIB_ARRAY_STRIDE => Int32Value(data.stride as i32),
+                constants::VERTEX_ATTRIB_ARRAY_ENABLED => {
+                    retval.set(BooleanValue(data.enabled_as_array))
+                },
+                constants::VERTEX_ATTRIB_ARRAY_SIZE => retval.set(Int32Value(data.size as i32)),
+                constants::VERTEX_ATTRIB_ARRAY_TYPE => retval.set(Int32Value(data.type_ as i32)),
+                constants::VERTEX_ATTRIB_ARRAY_NORMALIZED => {
+                    retval.set(BooleanValue(data.normalized))
+                },
+                constants::VERTEX_ATTRIB_ARRAY_STRIDE => retval.set(Int32Value(data.stride as i32)),
                 constants::VERTEX_ATTRIB_ARRAY_BUFFER_BINDING => unsafe {
-                    rooted!(in(*cx) let mut jsval = NullValue());
                     if let Some(buffer) = data.buffer() {
-                        buffer.to_jsval(*cx, jsval.handle_mut());
+                        buffer.to_jsval(*cx, retval);
+                    } else {
+                        retval.set(NullValue());
                     }
-                    jsval.get()
                 },
                 ANGLEInstancedArraysConstants::VERTEX_ATTRIB_ARRAY_DIVISOR_ANGLE => {
-                    UInt32Value(data.divisor)
+                    retval.set(UInt32Value(data.divisor))
                 },
                 _ => {
                     self.webgl_error(InvalidEnum);
-                    NullValue()
+                    retval.set(NullValue())
                 },
             }
         };
@@ -3479,7 +3556,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 let data = handle_potential_webgl_error!(
                     self,
                     current_vao.get_vertex_attrib(index).ok_or(InvalidValue),
-                    return NullValue()
+                    return retval.set(NullValue())
                 );
                 get_attrib(data)
             },
@@ -3488,7 +3565,7 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
                 let data = handle_potential_webgl_error!(
                     self,
                     current_vao.get_vertex_attrib(index).ok_or(InvalidValue),
-                    return NullValue()
+                    return retval.set(NullValue())
                 );
                 get_attrib(data)
             },
@@ -4062,61 +4139,92 @@ impl WebGLRenderingContextMethods for WebGLRenderingContext {
         cx: SafeJSContext,
         program: &WebGLProgram,
         location: &WebGLUniformLocation,
-    ) -> JSVal {
+        mut rval: MutableHandleValue,
+    ) {
         handle_potential_webgl_error!(
             self,
             self.uniform_check_program(program, location),
-            return NullValue()
+            return rval.set(NullValue())
         );
 
         let triple = (self, program.id(), location.id());
 
         match location.type_() {
-            constants::BOOL => BooleanValue(uniform_get(triple, WebGLCommand::GetUniformBool)),
+            constants::BOOL => rval.set(BooleanValue(uniform_get(
+                triple,
+                WebGLCommand::GetUniformBool,
+            ))),
             constants::BOOL_VEC2 => unsafe {
-                rooted!(in(*cx) let mut rval = NullValue());
-                uniform_get(triple, WebGLCommand::GetUniformBool2).to_jsval(*cx, rval.handle_mut());
-                rval.get()
+                uniform_get(triple, WebGLCommand::GetUniformBool2).to_jsval(*cx, rval);
             },
             constants::BOOL_VEC3 => unsafe {
-                rooted!(in(*cx) let mut rval = NullValue());
-                uniform_get(triple, WebGLCommand::GetUniformBool3).to_jsval(*cx, rval.handle_mut());
-                rval.get()
+                uniform_get(triple, WebGLCommand::GetUniformBool3).to_jsval(*cx, rval);
             },
             constants::BOOL_VEC4 => unsafe {
-                rooted!(in(*cx) let mut rval = NullValue());
-                uniform_get(triple, WebGLCommand::GetUniformBool4).to_jsval(*cx, rval.handle_mut());
-                rval.get()
+                uniform_get(triple, WebGLCommand::GetUniformBool4).to_jsval(*cx, rval);
             },
             constants::INT | constants::SAMPLER_2D | constants::SAMPLER_CUBE => {
-                Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt))
+                rval.set(Int32Value(uniform_get(triple, WebGLCommand::GetUniformInt)))
             },
             constants::INT_VEC2 => unsafe {
-                uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt2))
+                uniform_typed::<Int32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformInt2),
+                    rval,
+                )
             },
             constants::INT_VEC3 => unsafe {
-                uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt3))
+                uniform_typed::<Int32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformInt3),
+                    rval,
+                )
             },
             constants::INT_VEC4 => unsafe {
-                uniform_typed::<Int32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformInt4))
-            },
-            constants::FLOAT => {
-                DoubleValue(uniform_get(triple, WebGLCommand::GetUniformFloat) as f64)
+                uniform_typed::<Int32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformInt4),
+                    rval,
+                )
             },
+            constants::FLOAT => rval
+                .set(DoubleValue(
+                    uniform_get(triple, WebGLCommand::GetUniformFloat) as f64,
+                )),
             constants::FLOAT_VEC2 => unsafe {
-                uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat2))
+                uniform_typed::<Float32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformFloat2),
+                    rval,
+                )
             },
             constants::FLOAT_VEC3 => unsafe {
-                uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat3))
+                uniform_typed::<Float32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformFloat3),
+                    rval,
+                )
             },
             constants::FLOAT_VEC4 | constants::FLOAT_MAT2 => unsafe {
-                uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat4))
+                uniform_typed::<Float32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformFloat4),
+                    rval,
+                )
             },
             constants::FLOAT_MAT3 => unsafe {
-                uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat9))
+                uniform_typed::<Float32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformFloat9),
+                    rval,
+                )
             },
             constants::FLOAT_MAT4 => unsafe {
-                uniform_typed::<Float32>(*cx, &uniform_get(triple, WebGLCommand::GetUniformFloat16))
+                uniform_typed::<Float32>(
+                    *cx,
+                    &uniform_get(triple, WebGLCommand::GetUniformFloat16),
+                    rval,
+                )
             },
             _ => panic!("wrong uniform type"),
         }
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index e133eb6cb46b0..9121fa75fc088 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -33,10 +33,11 @@ use ipc_channel::ipc::{self, IpcSender};
 use ipc_channel::router::ROUTER;
 use js::conversions::ToJSValConvertible;
 use js::jsapi::{GCReason, Heap, JSAutoRealm, JSObject, StackFormat, JSPROP_ENUMERATE, JS_GC};
-use js::jsval::{JSVal, NullValue, UndefinedValue};
+use js::jsval::{NullValue, UndefinedValue};
 use js::rust::wrappers::JS_DefineProperty;
 use js::rust::{
     CustomAutoRooter, CustomAutoRooterGuard, HandleObject, HandleValue, MutableHandleObject,
+    MutableHandleValue,
 };
 use malloc_size_of::MallocSizeOf;
 use media::WindowGLContext;
@@ -697,22 +698,32 @@ impl WindowMethods for Window {
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-opener
-    fn GetOpener(&self, cx: JSContext, in_realm_proof: InRealm) -> Fallible<JSVal> {
+    fn GetOpener(
+        &self,
+        cx: JSContext,
+        in_realm_proof: InRealm,
+        mut retval: MutableHandleValue,
+    ) -> Fallible<()> {
         // Step 1, Let current be this Window object's browsing context.
         let current = match self.window_proxy.get() {
             Some(proxy) => proxy,
             // Step 2, If current is null, then return null.
-            None => return Ok(NullValue()),
+            None => {
+                retval.set(NullValue());
+                return Ok(());
+            },
         };
         // Still step 2, since the window's BC is the associated doc's BC,
         // see https://html.spec.whatwg.org/multipage/#window-bc
         // and a doc's BC is null if it has been discarded.
         // see https://html.spec.whatwg.org/multipage/#concept-document-bc
         if current.is_browsing_context_discarded() {
-            return Ok(NullValue());
+            retval.set(NullValue());
+            return Ok(());
         }
         // Step 3 to 5.
-        Ok(current.opener(*cx, in_realm_proof))
+        current.opener(*cx, in_realm_proof, retval);
+        Ok(())
     }
 
     #[allow(unsafe_code)]
@@ -1388,17 +1399,12 @@ impl WindowMethods for Window {
 
     // https://dom.spec.whatwg.org/#dom-window-event
     #[allow(unsafe_code)]
-    fn Event(&self, cx: JSContext) -> JSVal {
-        rooted!(in(*cx) let mut rval = UndefinedValue());
+    fn Event(&self, cx: JSContext, rval: MutableHandleValue) {
         if let Some(ref event) = *self.current_event.borrow() {
             unsafe {
-                event
-                    .reflector()
-                    .get_jsobject()
-                    .to_jsval(*cx, rval.handle_mut());
+                event.reflector().get_jsobject().to_jsval(*cx, rval);
             }
         }
-        rval.get()
     }
 
     fn IsSecureContext(&self) -> bool {
@@ -1571,9 +1577,10 @@ impl WindowMethods for Window {
         cx: JSContext,
         value: HandleValue,
         options: RootedTraceableBox<StructuredSerializeOptions>,
-    ) -> Fallible<js::jsval::JSVal> {
+        retval: MutableHandleValue,
+    ) -> Fallible<()> {
         self.upcast::<GlobalScope>()
-            .structured_clone(cx, value, options)
+            .structured_clone(cx, value, options, retval)
     }
 }
 
diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs
index 4d080881ba87d..0226747193071 100644
--- a/components/script/dom/windowproxy.rs
+++ b/components/script/dom/windowproxy.rs
@@ -24,9 +24,9 @@ use js::jsapi::{
     MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
     ObjectOpResult, PropertyDescriptor, JSPROP_ENUMERATE, JSPROP_READONLY,
 };
-use js::jsval::{JSVal, NullValue, PrivateValue, UndefinedValue};
+use js::jsval::{NullValue, PrivateValue, UndefinedValue};
 use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy};
-use js::rust::{get_object_class, Handle, MutableHandle};
+use js::rust::{get_object_class, Handle, MutableHandle, MutableHandleValue};
 use js::JSCLASS_IS_GLOBAL;
 use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
 use net_traits::request::Referrer;
@@ -416,13 +416,18 @@ impl WindowProxy {
 
     #[allow(unsafe_code)]
     // https://html.spec.whatwg.org/multipage/#dom-opener
-    pub fn opener(&self, cx: *mut JSContext, in_realm_proof: InRealm) -> JSVal {
+    pub fn opener(
+        &self,
+        cx: *mut JSContext,
+        in_realm_proof: InRealm,
+        mut retval: MutableHandleValue,
+    ) {
         if self.disowned.get() {
-            return NullValue();
+            return retval.set(NullValue());
         }
         let opener_id = match self.opener {
             Some(opener_browsing_context_id) => opener_browsing_context_id,
-            None => return NullValue(),
+            None => return retval.set(NullValue()),
         };
         let parent_browsing_context = self.parent.as_deref();
         let opener_proxy = match ScriptThread::find_window_proxy(opener_id) {
@@ -447,16 +452,14 @@ impl WindowProxy {
                             creator,
                         )
                     },
-                    None => return NullValue(),
+                    None => return retval.set(NullValue()),
                 }
             },
         };
         if opener_proxy.is_browsing_context_discarded() {
-            return NullValue();
+            return retval.set(NullValue());
         }
-        rooted!(in(cx) let mut val = UndefinedValue());
-        unsafe { opener_proxy.to_jsval(cx, val.handle_mut()) };
-        val.get()
+        unsafe { opener_proxy.to_jsval(cx, retval) };
     }
 
     // https://html.spec.whatwg.org/multipage/#window-open-steps
diff --git a/components/script/dom/workerglobalscope.rs b/components/script/dom/workerglobalscope.rs
index f89ff975322e5..b08cf0abc38d6 100644
--- a/components/script/dom/workerglobalscope.rs
+++ b/components/script/dom/workerglobalscope.rs
@@ -16,7 +16,7 @@ use dom_struct::dom_struct;
 use ipc_channel::ipc::IpcSender;
 use js::jsval::UndefinedValue;
 use js::panic::maybe_resume_unwind;
-use js::rust::{HandleValue, ParentRuntime};
+use js::rust::{HandleValue, MutableHandleValue, ParentRuntime};
 use net_traits::request::{
     CredentialsMode, Destination, ParserMetadata, RequestBuilder as NetRequestInit,
 };
@@ -439,9 +439,10 @@ impl WorkerGlobalScopeMethods for WorkerGlobalScope {
         cx: JSContext,
         value: HandleValue,
         options: RootedTraceableBox<StructuredSerializeOptions>,
-    ) -> Fallible<js::jsval::JSVal> {
+        retval: MutableHandleValue,
+    ) -> Fallible<()> {
         self.upcast::<GlobalScope>()
-            .structured_clone(cx, value, options)
+            .structured_clone(cx, value, options, retval)
     }
 }
 
diff --git a/components/script/dom/workernavigator.rs b/components/script/dom/workernavigator.rs
index aae6a59f7339d..795c764b8c68e 100644
--- a/components/script/dom/workernavigator.rs
+++ b/components/script/dom/workernavigator.rs
@@ -3,7 +3,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 
 use crate::dom::bindings::codegen::Bindings::WorkerNavigatorBinding::WorkerNavigatorMethods;
 use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
@@ -97,8 +97,8 @@ impl WorkerNavigatorMethods for WorkerNavigator {
 
     // https://html.spec.whatwg.org/multipage/#dom-navigator-languages
     #[allow(unsafe_code)]
-    fn Languages(&self, cx: JSContext) -> JSVal {
-        to_frozen_array(&[self.Language()], cx)
+    fn Languages(&self, cx: JSContext, retval: MutableHandleValue) {
+        to_frozen_array(&[self.Language()], cx, retval)
     }
 
     // https://w3c.github.io/permissions/#navigator-and-workernavigator-extension
diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs
index 74d99176f2661..0432794531304 100644
--- a/components/script/dom/xmlhttprequest.rs
+++ b/components/script/dom/xmlhttprequest.rs
@@ -20,9 +20,9 @@ use http::Method;
 use hyper_serde::Serde;
 use ipc_channel::ipc;
 use js::jsapi::{Heap, JS_ClearPendingException};
-use js::jsval::{JSVal, NullValue, UndefinedValue};
+use js::jsval::{JSVal, NullValue};
 use js::rust::wrappers::JS_ParseJSON;
-use js::rust::HandleObject;
+use js::rust::{HandleObject, MutableHandleValue};
 use js::typedarray::{ArrayBuffer, ArrayBufferU8};
 use mime::{self, Mime, Name};
 use net_traits::http_status::HttpStatus;
@@ -935,8 +935,7 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
 
     #[allow(unsafe_code)]
     /// <https://xhr.spec.whatwg.org/#the-response-attribute>
-    fn Response(&self, cx: JSContext, can_gc: CanGc) -> JSVal {
-        rooted!(in(*cx) let mut rval = UndefinedValue());
+    fn Response(&self, cx: JSContext, can_gc: CanGc, mut rval: MutableHandleValue) {
         match self.response_type.get() {
             XMLHttpRequestResponseType::_empty | XMLHttpRequestResponseType::Text => unsafe {
                 let ready_state = self.ready_state.get();
@@ -944,33 +943,29 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
                 if ready_state == XMLHttpRequestState::Done ||
                     ready_state == XMLHttpRequestState::Loading
                 {
-                    self.text_response().to_jsval(*cx, rval.handle_mut());
+                    self.text_response().to_jsval(*cx, rval);
                 } else {
                     // Step 1
-                    "".to_jsval(*cx, rval.handle_mut());
+                    "".to_jsval(*cx, rval);
                 }
             },
             // Step 1
             _ if self.ready_state.get() != XMLHttpRequestState::Done => {
-                return NullValue();
+                rval.set(NullValue());
             },
             // Step 2
             XMLHttpRequestResponseType::Document => unsafe {
-                self.document_response(can_gc)
-                    .to_jsval(*cx, rval.handle_mut());
-            },
-            XMLHttpRequestResponseType::Json => unsafe {
-                self.json_response(cx).to_jsval(*cx, rval.handle_mut());
+                self.document_response(can_gc).to_jsval(*cx, rval);
             },
+            XMLHttpRequestResponseType::Json => self.json_response(cx, rval),
             XMLHttpRequestResponseType::Blob => unsafe {
-                self.blob_response(can_gc).to_jsval(*cx, rval.handle_mut());
+                self.blob_response(can_gc).to_jsval(*cx, rval);
             },
             XMLHttpRequestResponseType::Arraybuffer => match self.arraybuffer_response(cx) {
-                Some(array_buffer) => unsafe { array_buffer.to_jsval(*cx, rval.handle_mut()) },
-                None => return NullValue(),
+                Some(array_buffer) => unsafe { array_buffer.to_jsval(*cx, rval) },
+                None => rval.set(NullValue()),
             },
         }
-        rval.get()
     }
 
     /// <https://xhr.spec.whatwg.org/#the-responsetext-attribute>
@@ -1447,17 +1442,17 @@ impl XMLHttpRequest {
 
     #[allow(unsafe_code)]
     /// <https://xhr.spec.whatwg.org/#json-response>
-    fn json_response(&self, cx: JSContext) -> JSVal {
+    fn json_response(&self, cx: JSContext, mut rval: MutableHandleValue) {
         // Step 1
         let response_json = self.response_json.get();
         if !response_json.is_null_or_undefined() {
-            return response_json;
+            return rval.set(response_json);
         }
         // Step 2
         let bytes = self.response.borrow();
         // Step 3
         if bytes.len() == 0 {
-            return NullValue();
+            return rval.set(NullValue());
         }
         // Step 4
         fn decode_to_utf16_with_bom_removal(bytes: &[u8], encoding: &'static Encoding) -> Vec<u16> {
@@ -1480,21 +1475,14 @@ impl XMLHttpRequest {
         // if present, but UTF-16BE/LE BOM must not be honored.
         let json_text = decode_to_utf16_with_bom_removal(&bytes, UTF_8);
         // Step 5
-        rooted!(in(*cx) let mut rval = UndefinedValue());
         unsafe {
-            if !JS_ParseJSON(
-                *cx,
-                json_text.as_ptr(),
-                json_text.len() as u32,
-                rval.handle_mut(),
-            ) {
+            if !JS_ParseJSON(*cx, json_text.as_ptr(), json_text.len() as u32, rval) {
                 JS_ClearPendingException(*cx);
-                return NullValue();
+                return rval.set(NullValue());
             }
         }
         // Step 6
         self.response_json.set(rval.get());
-        self.response_json.get()
     }
 
     fn document_text_html(&self, can_gc: CanGc) -> DomRoot<Document> {
diff --git a/components/script/dom/xrboundedreferencespace.rs b/components/script/dom/xrboundedreferencespace.rs
index 93d3d5d10c706..87c267152c189 100644
--- a/components/script/dom/xrboundedreferencespace.rs
+++ b/components/script/dom/xrboundedreferencespace.rs
@@ -3,7 +3,7 @@
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 
 use crate::dom::bindings::codegen::Bindings::XRBoundedReferenceSpaceBinding::XRBoundedReferenceSpaceMethods;
 use crate::dom::bindings::codegen::Bindings::XRReferenceSpaceBinding::XRReferenceSpaceType;
@@ -67,7 +67,7 @@ impl XRBoundedReferenceSpace {
 
 impl XRBoundedReferenceSpaceMethods for XRBoundedReferenceSpace {
     /// <https://www.w3.org/TR/webxr/#dom-xrboundedreferencespace-boundsgeometry>
-    fn BoundsGeometry(&self, cx: JSContext, can_gc: CanGc) -> JSVal {
+    fn BoundsGeometry(&self, cx: JSContext, can_gc: CanGc, retval: MutableHandleValue) {
         if let Some(bounds) = self.reference_space.get_bounds() {
             let points: Vec<DomRoot<DOMPointReadOnly>> = bounds
                 .into_iter()
@@ -83,9 +83,9 @@ impl XRBoundedReferenceSpaceMethods for XRBoundedReferenceSpace {
                 })
                 .collect();
 
-            to_frozen_array(&points, cx)
+            to_frozen_array(&points, cx, retval)
         } else {
-            to_frozen_array::<DomRoot<DOMPointReadOnly>>(&[], cx)
+            to_frozen_array::<DomRoot<DOMPointReadOnly>>(&[], cx, retval)
         }
     }
 }
diff --git a/components/script/dom/xrinputsource.rs b/components/script/dom/xrinputsource.rs
index a260e2e678020..abbb65bdc9b46 100644
--- a/components/script/dom/xrinputsource.rs
+++ b/components/script/dom/xrinputsource.rs
@@ -6,6 +6,7 @@ use dom_struct::dom_struct;
 use js::conversions::ToJSValConvertible;
 use js::jsapi::Heap;
 use js::jsval::{JSVal, UndefinedValue};
+use js::rust::MutableHandleValue;
 use script_traits::GamepadSupportedHapticEffects;
 use webxr_api::{Handedness, InputFrame, InputId, InputSource, TargetRayMode};
 
@@ -159,8 +160,8 @@ impl XRInputSourceMethods for XRInputSource {
         }
     }
     // https://immersive-web.github.io/webxr/#dom-xrinputsource-profiles
-    fn Profiles(&self, _cx: JSContext) -> JSVal {
-        self.profiles.get()
+    fn Profiles(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.profiles.get())
     }
 
     /// <https://www.w3.org/TR/webxr/#dom-xrinputsource-skiprendering>
diff --git a/components/script/dom/xrinputsourceschangeevent.rs b/components/script/dom/xrinputsourceschangeevent.rs
index dc28cefa812de..1ae14b3a47c60 100644
--- a/components/script/dom/xrinputsourceschangeevent.rs
+++ b/components/script/dom/xrinputsourceschangeevent.rs
@@ -5,7 +5,7 @@
 use dom_struct::dom_struct;
 use js::jsapi::Heap;
 use js::jsval::JSVal;
-use js::rust::HandleObject;
+use js::rust::{HandleObject, MutableHandleValue};
 use servo_atoms::Atom;
 
 use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods;
@@ -87,8 +87,11 @@ impl XRInputSourcesChangeEvent {
         }
         let _ac = enter_realm(global);
         let cx = GlobalScope::get_cx();
-        changeevent.added.set(to_frozen_array(added, cx));
-        changeevent.removed.set(to_frozen_array(removed, cx));
+        rooted!(in(*cx) let mut frozen_val: JSVal);
+        to_frozen_array(added, cx, frozen_val.handle_mut());
+        changeevent.added.set(*frozen_val);
+        to_frozen_array(removed, cx, frozen_val.handle_mut());
+        changeevent.removed.set(*frozen_val);
         changeevent
     }
 }
@@ -121,13 +124,13 @@ impl XRInputSourcesChangeEventMethods for XRInputSourcesChangeEvent {
     }
 
     // https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-added
-    fn Added(&self, _cx: JSContext) -> JSVal {
-        self.added.get()
+    fn Added(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.added.get())
     }
 
     // https://immersive-web.github.io/webxr/#dom-xrinputsourceschangeevent-removed
-    fn Removed(&self, _cx: JSContext) -> JSVal {
-        self.removed.get()
+    fn Removed(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.removed.get())
     }
 
     // https://dom.spec.whatwg.org/#dom-event-istrusted
diff --git a/components/script/dom/xrrenderstate.rs b/components/script/dom/xrrenderstate.rs
index cabe248d0a2ca..e189710ead287 100644
--- a/components/script/dom/xrrenderstate.rs
+++ b/components/script/dom/xrrenderstate.rs
@@ -5,7 +5,7 @@
 use std::cell::Cell;
 
 use dom_struct::dom_struct;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 use webxr_api::SubImages;
 
 use crate::dom::bindings::cell::DomRefCell;
@@ -146,10 +146,10 @@ impl XRRenderStateMethods for XRRenderState {
     }
 
     /// <https://immersive-web.github.io/layers/#dom-xrrenderstate-layers>
-    fn Layers(&self, cx: JSContext) -> JSVal {
+    fn Layers(&self, cx: JSContext, retval: MutableHandleValue) {
         // TODO: cache this array?
         let layers = self.layers.borrow();
         let layers: Vec<&XRLayer> = layers.iter().map(|x| &**x).collect();
-        to_frozen_array(&layers[..], cx)
+        to_frozen_array(&layers[..], cx, retval)
     }
 }
diff --git a/components/script/dom/xrsession.rs b/components/script/dom/xrsession.rs
index 85a7ec7ae4177..6ba00e003a760 100644
--- a/components/script/dom/xrsession.rs
+++ b/components/script/dom/xrsession.rs
@@ -14,7 +14,7 @@ use euclid::{RigidTransform3D, Transform3D, Vector3D};
 use ipc_channel::ipc::IpcReceiver;
 use ipc_channel::router::ROUTER;
 use js::jsapi::JSObject;
-use js::jsval::JSVal;
+use js::rust::MutableHandleValue;
 use js::typedarray::Float32Array;
 use profile_traits::ipc;
 use servo_atoms::Atom;
@@ -1011,10 +1011,10 @@ impl XRSessionMethods for XRSession {
     }
 
     /// <https://www.w3.org/TR/webxr/#dom-xrsession-enabledfeatures>
-    fn EnabledFeatures(&self, cx: JSContext) -> JSVal {
+    fn EnabledFeatures(&self, cx: JSContext, retval: MutableHandleValue) {
         let session = self.session.borrow();
         let features = session.granted_features();
-        to_frozen_array(features, cx)
+        to_frozen_array(features, cx, retval)
     }
 
     /// <https://www.w3.org/TR/webxr/#dom-xrsession-issystemkeyboardsupported>
diff --git a/components/script/dom/xrviewerpose.rs b/components/script/dom/xrviewerpose.rs
index 92d2b03b54d61..9c8774c7855b5 100644
--- a/components/script/dom/xrviewerpose.rs
+++ b/components/script/dom/xrviewerpose.rs
@@ -7,6 +7,7 @@ use euclid::RigidTransform3D;
 use js::conversions::ToJSValConvertible;
 use js::jsapi::Heap;
 use js::jsval::{JSVal, UndefinedValue};
+use js::rust::MutableHandleValue;
 use webxr_api::{Viewer, ViewerPose, Views};
 
 use crate::dom::bindings::codegen::Bindings::XRViewBinding::XREye;
@@ -189,7 +190,7 @@ impl XRViewerPose {
 
 impl XRViewerPoseMethods for XRViewerPose {
     /// <https://immersive-web.github.io/webxr/#dom-xrviewerpose-views>
-    fn Views(&self, _cx: JSContext) -> JSVal {
-        self.views.get()
+    fn Views(&self, _cx: JSContext, mut retval: MutableHandleValue) {
+        retval.set(self.views.get())
     }
 }
diff --git a/components/script/timers.rs b/components/script/timers.rs
index b10a356f7c99f..d3148f8f00dac 100644
--- a/components/script/timers.rs
+++ b/components/script/timers.rs
@@ -563,7 +563,8 @@ impl JsTimerTask {
             },
             InternalTimerCallback::FunctionTimerCallback(ref function, ref arguments) => {
                 let arguments = self.collect_heap_args(arguments);
-                let _ = function.Call_(this, arguments, Report);
+                rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
+                let _ = function.Call_(this, arguments, value.handle_mut(), Report);
             },
         };
         ScriptThread::set_user_interacting(was_user_interacting);


Test patch:
diff --git a/components/script/dom/testbinding.rs b/components/script/dom/testbinding.rs
index 2c91ce7fac5b6..1fc70eac60dbe 100644
--- a/components/script/dom/testbinding.rs
+++ b/components/script/dom/testbinding.rs
@@ -11,8 +11,8 @@ use std::time::Duration;
 
 use dom_struct::dom_struct;
 use js::jsapi::{Heap, JSObject, JS_NewPlainObject};
-use js::jsval::{JSVal, NullValue};
-use js::rust::{CustomAutoRooterGuard, HandleObject, HandleValue};
+use js::jsval::JSVal;
+use js::rust::{CustomAutoRooterGuard, HandleObject, HandleValue, MutableHandleValue};
 use js::typedarray::{self, Uint8ClampedArray};
 use script_traits::serializable::BlobImpl;
 use servo_config::prefs;
@@ -229,9 +229,7 @@ impl TestBindingMethods for TestBinding {
         create_buffer_source(cx, &data, array.handle_mut())
             .expect("Creating ClampedU8 array should never fail")
     }
-    fn AnyAttribute(&self, _: SafeJSContext) -> JSVal {
-        NullValue()
-    }
+    fn AnyAttribute(&self, _: SafeJSContext, _: MutableHandleValue) {}
     fn SetAnyAttribute(&self, _: SafeJSContext, _: HandleValue) {}
     #[allow(unsafe_code)]
     fn ObjectAttribute(&self, cx: SafeJSContext) -> NonNull<JSObject> {
@@ -426,9 +424,7 @@ impl TestBindingMethods for TestBinding {
             can_gc,
         )
     }
-    fn ReceiveAny(&self, _: SafeJSContext) -> JSVal {
-        NullValue()
-    }
+    fn ReceiveAny(&self, _: SafeJSContext, _: MutableHandleValue) {}
     fn ReceiveObject(&self, cx: SafeJSContext) -> NonNull<JSObject> {
         self.ObjectAttribute(cx)
     }
diff --git a/components/script/dom/xrtest.rs b/components/script/dom/xrtest.rs
index b50549d8be29c..c6cc3a3ee7c5b 100644
--- a/components/script/dom/xrtest.rs
+++ b/components/script/dom/xrtest.rs
@@ -11,6 +11,7 @@ use std::rc::Rc;
 use dom_struct::dom_struct;
 use ipc_channel::ipc::IpcSender;
 use ipc_channel::router::ROUTER;
+use js::jsval::JSVal;
 use profile_traits::ipc;
 use webxr_api::{self, Error as XRError, MockDeviceInit, MockDeviceMsg};
 
@@ -184,7 +185,8 @@ impl XRTestMethods for XRTest {
     /// <https://github.com/immersive-web/webxr-test-api/blob/master/explainer.md>
     fn SimulateUserActivation(&self, f: Rc<Function>) {
         ScriptThread::set_user_interacting(true);
-        let _ = f.Call__(vec![], ExceptionHandling::Rethrow);
+        rooted!(in(*GlobalScope::get_cx()) let mut value: JSVal);
+        let _ = f.Call__(vec![], value.handle_mut(), ExceptionHandling::Rethrow);
         ScriptThread::set_user_interacting(false);
     }