Bug 1507184 - API tweaks to new Unwrap templates. r=tcampbell
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 16 Nov 2018 13:31:17 +0000
changeset 503215 e3e05897de25a3b5d4d8d41ac5c20463c62153e0
parent 503214 79c527fa7f0f2c2ed645f59a30f2bc105e2868d4
child 503216 624e45ad97c89acc0b916c3c7c53b95cdb1f5014
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstcampbell
bugs1507184
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1507184 - API tweaks to new Unwrap templates. r=tcampbell Differential Revision: https://phabricator.services.mozilla.com/D11896
js/src/builtin/Stream.cpp
js/src/vm/Compartment-inl.h
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -89,41 +89,36 @@ static T*
 ToUnwrapped(JSContext* cx, HandleValue v)
 {
     return ToUnwrapped<T>(cx, &v.toObject());
 }
 
 /**
  * Returns the stream associated with the given reader.
  */
-static MOZ_MUST_USE bool
-UnwrapStreamFromReader(JSContext *cx,
-                       Handle<ReadableStreamReader*> reader,
-                       MutableHandle<ReadableStream*> unwrappedResult)
+static MOZ_MUST_USE ReadableStream*
+UnwrapStreamFromReader(JSContext *cx, Handle<ReadableStreamReader*> reader)
 {
     MOZ_ASSERT(reader->hasStream());
-    return UnwrapInternalSlot(cx, reader, ReadableStreamReader::Slot_Stream, unwrappedResult);
+    return UnwrapInternalSlot<ReadableStream>(cx, reader, ReadableStreamReader::Slot_Stream);
 }
 
 /**
  * Returns the reader associated with the given stream.
  *
  * Must only be called on ReadableStreams that already have a reader
  * associated with them.
  *
- * If the reader is a wrapper, it will be unwrapped, so the object stored in
- * `unwrappedResult` might not be an object from the currently active
- * compartment.
+ * If the reader is a wrapper, it will be unwrapped, so the result might not be
+ * an object from the currently active compartment.
  */
-static MOZ_MUST_USE bool
-UnwrapReaderFromStream(JSContext* cx,
-                       Handle<ReadableStream*> stream,
-                       MutableHandle<ReadableStreamReader*> unwrappedResult)
+static MOZ_MUST_USE ReadableStreamReader*
+UnwrapReaderFromStream(JSContext* cx, Handle<ReadableStream*> stream)
 {
-    return UnwrapInternalSlot(cx, stream, ReadableStream::Slot_Reader, unwrappedResult);
+    return UnwrapInternalSlot<ReadableStreamReader>(cx, stream, ReadableStream::Slot_Reader);
 }
 
 static MOZ_MUST_USE ReadableStreamReader*
 UnwrapReaderFromStreamNoThrow(ReadableStream* stream)
 {
     JSObject* readerObj = &stream->getFixedSlot(ReadableStream::Slot_Reader).toObject();
     if (IsProxy(readerObj)) {
         if (JS_IsDeadWrapper(readerObj)) {
@@ -691,23 +686,19 @@ ReadableStream::constructor(JSContext* c
  * Streams spec, 3.2.5.1. get locked
  */
 static bool
 ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStream",
-                                       "get locked",
-                                       &unwrappedStream))
-    {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "get locked"));
+    if (!unwrappedStream) {
         return false;
     }
 
     // Step 2: Return ! IsReadableStreamLocked(this).
     args.rval().setBoolean(unwrappedStream->locked());
     return true;
 }
 
@@ -719,20 +710,19 @@ ReadableStreamCancel(JSContext* cx, Hand
  */
 static MOZ_MUST_USE bool
 ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
     //         with a TypeError exception.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapThisForNonGenericMethod(cx, args.thisv(), "ReadableStream", "cancel",
-                                       &unwrappedStream))
-    {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "cancel"));
+    if (!unwrappedStream) {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
     //         rejected with a TypeError exception.
     if (unwrappedStream->locked()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAM_LOCKED_METHOD, "cancel");
@@ -755,20 +745,19 @@ CreateReadableStreamDefaultReader(JSCont
  * Streams spec, 3.2.5.3. getReader()
  */
 static bool
 ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapThisForNonGenericMethod(cx, args.thisv(), "ReadableStream", "getReader",
-                                       &unwrappedStream))
-    {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "getReader"));
+    if (!unwrappedStream) {
         return false;
     }
 
     RootedObject reader(cx);
 
     // Step 2: If mode is undefined, return
     //         ? AcquireReadableStreamDefaultReader(this).
     RootedValue modeVal(cx);
@@ -826,20 +815,19 @@ ReadableStreamTee(JSContext* cx, Handle<
  * Streams spec, 3.2.5.6. tee()
  */
 static bool
 ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapThisForNonGenericMethod(cx, args.thisv(), "ReadableStream", "tee",
-                                       &unwrappedStream))
-    {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapAndTypeCheckThis<ReadableStream>(cx, args, "tee"));
+    if (!unwrappedStream) {
         return false;
     }
 
     // Step 2: Let branches be ? ReadableStreamTee(this, false).
     Rooted<ReadableStream*> branch1(cx);
     Rooted<ReadableStream*> branch2(cx);
     if (!ReadableStreamTee(cx, unwrappedStream, false, &branch1, &branch2)) {
         return false;
@@ -1023,22 +1011,24 @@ ReadableStreamTee_Pull(JSContext* cx, Ha
     // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]],
     //         branch2 be F.[[branch2]], teeState be F.[[teeState]], and
     //         cloneForBranch2 be F.[[cloneForBranch2]].
 
     // Step 2: Return the result of transforming
     //         ! ReadableStreamDefaultReaderRead(reader) by a fulfillment
     //         handler which takes the argument result and performs the
     //         following steps:
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapInternalSlot(cx, unwrappedTeeState, TeeState::Slot_Stream, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState, TeeState::Slot_Stream));
+    if (!unwrappedStream) {
         return nullptr;
     }
-    Rooted<ReadableStreamReader*> unwrappedReaderObj(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReaderObj)) {
+    Rooted<ReadableStreamReader*> unwrappedReaderObj(cx,
+        UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReaderObj) {
         return nullptr;
     }
 
     Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx,
         &unwrappedReaderObj->as<ReadableStreamDefaultReader>());
 
     RootedObject readPromise(cx, ::ReadableStreamDefaultReaderRead(cx, unwrappedReader));
     if (!readPromise) {
@@ -1062,18 +1052,19 @@ ReadableStreamTee_Pull(JSContext* cx, Ha
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamTee_Cancel(JSContext* cx,
                          Handle<TeeState*> unwrappedTeeState,
                          Handle<ReadableStreamDefaultController*> unwrappedBranch,
                          HandleValue reason)
 {
     // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]].
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapInternalSlot(cx, unwrappedTeeState, TeeState::Slot_Stream, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapInternalSlot<ReadableStream>(cx, unwrappedTeeState, TeeState::Slot_Stream));
+    if (!unwrappedStream) {
         return nullptr;
     }
 
     bool bothBranchesCanceled = false;
 
     // Step 2: Set teeState.[[canceled1]] to true.
     // Step 3: Set teeState.[[reason1]] to reason.
     {
@@ -1299,18 +1290,18 @@ AppendToListAtSlot(JSContext* cx,
  * Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream )
  * Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream )
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamAddReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> unwrappedStream)
 {
     // Step 1: Assert: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
     // Skipped: handles both kinds of readers.
-    Rooted<ReadableStreamReader*> unwrappedReader(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReader)) {
+    Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReader) {
         return nullptr;
     }
 
     // Step 2 of 3.4.2: Assert: stream.[[state]] is "readable".
     MOZ_ASSERT_IF(unwrappedReader->is<ReadableStreamDefaultReader>(), unwrappedStream->readable());
 
     // Step 3: Let promise be a new promise.
     RootedObject promise(cx, PromiseObject::createSkippingExecutor(cx));
@@ -1413,18 +1404,18 @@ ReadableStreamCloseInternal(JSContext* c
     unwrappedStream->setClosed();
 
     // Step 4: If reader is undefined, return (reordered).
     if (!unwrappedStream->hasReader()) {
         return true;
     }
 
     // Step 3: Let reader be stream.[[reader]].
-    Rooted<ReadableStreamReader*> unwrappedReader(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReader)) {
+    Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReader) {
         return false;
     }
 
     // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
     if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
         // Step a: Repeat for each readRequest that is an element of
         //         reader.[[readRequests]],
         RootedNativeObject unwrappedReadRequests(cx, unwrappedReader->requests());
@@ -1505,18 +1496,18 @@ ReadableStreamErrorInternal(JSContext* c
     }
 
     // Step 6: If reader is undefined, return (reordered).
     if (!unwrappedStream->hasReader()) {
         return true;
     }
 
     // Step 5: Let reader be stream.[[reader]].
-    Rooted<ReadableStreamReader*> unwrappedReader(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReader)) {
+    Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReader) {
         return false;
     }
 
     // Steps 7,8: (Identical in our implementation.)
     // Step a: Repeat for each readRequest that is an element of
     //         reader.[[readRequests]],
     RootedNativeObject unwrappedReadRequests(cx, unwrappedReader->requests());
     RootedObject readRequest(cx);
@@ -1592,18 +1583,18 @@ static MOZ_MUST_USE bool
 ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx,
                                            Handle<ReadableStream*> unwrappedStream,
                                            HandleValue chunk,
                                            bool done)
 {
     cx->check(chunk);
 
     // Step 1: Let reader be stream.[[reader]].
-    Rooted<ReadableStreamReader*> unwrappedReader(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReader)) {
+    Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReader) {
         return false;
     }
 
     // Step 2: Let readIntoRequest be the first element of
     //         reader.[[readIntoRequests]].
     // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
     //         all other elements downward (so that the second becomes the first,
     //         and so on).
@@ -1661,18 +1652,18 @@ ReadableStreamHasDefaultReader(JSContext
     // Step 2: If reader is undefined, return false.
     if (!unwrappedStream->hasReader()) {
         *result = false;
         return true;
     }
 
     // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
     // Step 4: Return true.
-    Rooted<ReadableStreamReader*> unwrappedReader(cx);
-    if (!UnwrapReaderFromStream(cx, unwrappedStream, &unwrappedReader)) {
+    Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
+    if (!unwrappedReader) {
         return false;
     }
 
     *result = unwrappedReader->is<ReadableStreamDefaultReader>();
     return true;
 }
 
 
@@ -1724,20 +1715,22 @@ ReadableStreamDefaultReader::constructor
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
         return false;
     }
 
     // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapAndTypeCheckArgument(cx, args, "ReadableStreamDefaultReader constructor", 0,
-                                    &unwrappedStream))
-    {
+    Rooted<ReadableStream*> unwrappedStream(cx,
+        UnwrapAndTypeCheckArgument<ReadableStream>(cx,
+                                                   args,
+                                                   "ReadableStreamDefaultReader constructor",
+                                                   0));
+    if (!unwrappedStream) {
         return false;
     }
 
     RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, unwrappedStream));
     if (!reader) {
         return false;
     }
 
@@ -1750,23 +1743,19 @@ ReadableStreamDefaultReader::constructor
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultReader_closed(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
     //         rejected with a TypeError exception.
-    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultReader",
-                                       "get closed",
-                                       &unwrappedReader))
-    {
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "get closed"));
+    if (!unwrappedReader) {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: Return this.[[closedPromise]].
     RootedObject closedPromise(cx, unwrappedReader->closedPromise());
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
         return false;
     }
@@ -1785,23 +1774,19 @@ ReadableStreamReaderGenericCancel(JSCont
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultReader_cancel(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
     //         rejected with a TypeError exception.
-    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultReader",
-                                       "cancel",
-                                       &unwrappedReader))
-    {
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "cancel"));
+    if (!unwrappedReader) {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
     //         rejected with a TypeError exception.
     if (!unwrappedReader->hasStream()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
@@ -1822,23 +1807,19 @@ ReadableStreamDefaultReader_cancel(JSCon
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultReader_read(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // Step 1: If ! IsReadableStreamDefaultReader(this) is false, return a promise
     //         rejected with a TypeError exception.
-    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultReader",
-                                       "read",
-                                       &unwrappedReader))
-    {
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "read"));
+    if (!unwrappedReader) {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
     //         rejected with a TypeError exception.
     if (!unwrappedReader->hasStream()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
@@ -1861,23 +1842,19 @@ ReadableStreamReaderGenericRelease(JSCon
  * Streams spec, 3.5.4.4. releaseLock ( )
  */
 static bool
 ReadableStreamDefaultReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: If ! IsReadableStreamDefaultReader(this) is false,
     //         throw a TypeError exception.
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamDefaultReader*> reader(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultReader",
-                                       "releaseLock",
-                                       &reader))
-    {
+    Rooted<ReadableStreamDefaultReader*> reader(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultReader>(cx, args, "releaseLock"));
+    if (!reader) {
         return false;
     }
 
     // Step 2: If this.[[ownerReadableStream]] is undefined, return.
     if (!reader->hasStream()) {
         args.rval().setUndefined();
         return true;
     }
@@ -1937,18 +1914,18 @@ CLASS_SPEC(ReadableStreamDefaultReader, 
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamReaderGenericCancel(JSContext* cx,
                                   Handle<ReadableStreamReader*> unwrappedReader,
                                   HandleValue reason)
 {
     // Step 1: Let stream be reader.[[ownerReadableStream]].
     // Step 2: Assert: stream is not undefined (implicit).
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx, UnwrapStreamFromReader(cx, unwrappedReader));
+    if (!unwrappedStream) {
         return nullptr;
     }
 
     // Step 3: Return ! ReadableStreamCancel(stream, reason).
     return ::ReadableStreamCancel(cx, unwrappedStream, reason);
 }
 
 /**
@@ -2014,18 +1991,18 @@ ReadableStreamReaderGenericInitialize(JS
 
 /**
  * Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader )
  */
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericRelease(JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader)
 {
     // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx, UnwrapStreamFromReader(cx, unwrappedReader));
+    if (!unwrappedStream) {
         return false;
     }
 
     // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
     MOZ_ASSERT(UnwrapReaderFromStreamNoThrow(unwrappedStream) == unwrappedReader);
 
     // Create an exception to reject promises with below. We don't have a
     // clean way to do this, unfortunately.
@@ -2035,22 +2012,21 @@ ReadableStreamReaderGenericRelease(JSCon
         // Uncatchable error. Die immediately without resolving
         // reader.[[closedPromise]].
         return false;
     }
 
     // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
     //         reader.[[closedPromise]] with a TypeError exception.
     if (unwrappedStream->readable()) {
-        Rooted<PromiseObject*> closedPromise(cx);
-        if (!UnwrapInternalSlot(cx,
-                                unwrappedReader,
-                                ReadableStreamReader::Slot_ClosedPromise,
-                                &closedPromise))
-        {
+        Rooted<PromiseObject*> closedPromise(cx,
+            UnwrapInternalSlot<PromiseObject>(cx,
+                                              unwrappedReader,
+                                              ReadableStreamReader::Slot_ClosedPromise));
+        if (!closedPromise) {
             return false;
         }
 
         AutoRealm ar(cx, closedPromise);
         if (!cx->compartment()->wrap(cx, &exn)) {
             return false;
         }
         if (!PromiseObject::reject(cx, closedPromise, exn)) {
@@ -2088,18 +2064,18 @@ ReadableStreamControllerPullSteps(JSCont
  * Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamDefaultReaderRead(JSContext* cx,
                                 Handle<ReadableStreamDefaultReader*> unwrappedReader)
 {
     // Step 1: Let stream be reader.[[ownerReadableStream]].
     // Step 2: Assert: stream is not undefined.
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx, UnwrapStreamFromReader(cx, unwrappedReader));
+    if (!unwrappedStream) {
         return nullptr;
     }
 
     // Step 3: Set stream.[[disturbed]] to true.
     unwrappedStream->setDisturbed();
 
     // Step 4: If stream.[[state]] is "closed", return a new promise resolved with
     //         ! CreateIterResultObject(undefined, true).
@@ -2322,23 +2298,19 @@ ReadableStreamControllerGetDesiredSizeUn
  * Streams spec, 3.10.4.2. get desiredSize
  */
 static bool
 ReadableStreamDefaultController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
     //         TypeError exception.
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamController*> unwrappedController(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultController",
-                                       "get desiredSize",
-                                       &unwrappedController))
-    {
+    Rooted<ReadableStreamController*> unwrappedController(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, "get desiredSize"));
+    if (!unwrappedController) {
         return false;
     }
 
     // Streams spec, 3.9.8. steps 1-4.
     // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
     ReadableStream* unwrappedStream = unwrappedController->stream();
 
     // 3.9.8. Step 2: Let state be stream.[[state]].
@@ -2393,23 +2365,19 @@ VerifyControllerStateForClosing(JSContex
  * Streams spec, 3.8.4.2 close()
  */
 static bool
 ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
     //         TypeError exception.
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultController",
-                                       "close",
-                                       &unwrappedController))
-    {
+    Rooted<ReadableStreamDefaultController*> unwrappedController(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, "close"));
+    if (!unwrappedController) {
         return false;
     }
 
     // Steps 2-3.
     if (!VerifyControllerStateForClosing(cx, unwrappedController)) {
         return false;
     }
 
@@ -2425,23 +2393,19 @@ ReadableStreamDefaultController_close(JS
  * Streams spec, 3.8.4.3. enqueue ( chunk )
  */
 static bool
 ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
     //         TypeError exception.
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultController",
-                                       "enqueue",
-                                       &unwrappedController))
-    {
+    Rooted<ReadableStreamDefaultController*> unwrappedController(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, "enqueue"));
+    if (!unwrappedController) {
         return false;
     }
 
     // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
     if (unwrappedController->closeRequested()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
         return false;
@@ -2468,23 +2432,19 @@ ReadableStreamDefaultController_enqueue(
  */
 static bool
 ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
     //         TypeError exception.
 
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamDefaultController*> unwrappedController(cx);
-    if (!UnwrapThisForNonGenericMethod(cx,
-                                       args.thisv(),
-                                       "ReadableStreamDefaultController",
-                                       "enqueue",
-                                       &unwrappedController))
-    {
+    Rooted<ReadableStreamDefaultController*> unwrappedController(cx,
+        UnwrapAndTypeCheckThis<ReadableStreamDefaultController>(cx, args, "enqueue"));
+    if (!unwrappedController) {
         return false;
     }
 
     // Step 2: Let stream be this.[[controlledReadableStream]].
     // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
     if (!unwrappedController->stream()->readable()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
@@ -4563,18 +4523,18 @@ JS::ReadableStreamReaderReleaseLock(JSCo
     CHECK_THREAD(cx);
 
     Rooted<ReadableStreamReader*> unwrappedReader(cx, APIToUnwrapped<ReadableStreamReader>(cx, readerObj));
     if (!unwrappedReader) {
         return false;
     }
 
 #ifdef DEBUG
-    Rooted<ReadableStream*> unwrappedStream(cx);
-    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx, UnwrapStreamFromReader(cx, unwrappedReader));
+    if (!unwrappedStream) {
         return false;
     }
     MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0);
 #endif // DEBUG
 
     return ReadableStreamReaderGenericRelease(cx, unwrappedReader);
 }
 
--- a/js/src/vm/Compartment-inl.h
+++ b/js/src/vm/Compartment-inl.h
@@ -105,170 +105,144 @@ JS::Compartment::wrap(JSContext* cx, JS:
     vp.setObject(*obj);
     MOZ_ASSERT_IF(cacheResult, obj == cacheResult);
     return true;
 }
 
 namespace js {
 namespace detail {
 
+/**
+ * Return the name of class T as a static null-terminated ASCII string constant
+ * (for error messages).
+ */
 template <class T>
-bool
-UnwrapThisSlowPath(JSContext* cx,
-                   HandleValue val,
-                   const char* className,
-                   const char* methodName,
-                   MutableHandle<T*> unwrappedResult)
+const char *
+ClassName()
+{
+    return T::class_.name;
+}
+
+template <class T>
+MOZ_MUST_USE T*
+UnwrapAndTypeCheckThisSlowPath(JSContext* cx,
+                               HandleValue val,
+                               const char* methodName)
 {
     if (!val.isObject()) {
         JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                                   className, methodName, InformalValueTypeName(val));
-        return false;
+                                   ClassName<T>(), methodName, InformalValueTypeName(val));
+        return nullptr;
     }
 
     JSObject* obj = &val.toObject();
     if (IsWrapper(obj)) {
         obj = CheckedUnwrap(obj);
         if (!obj) {
             ReportAccessDenied(cx);
-            return false;
+            return nullptr;
         }
     }
 
     if (!obj->is<T>()) {
         JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                                   className, methodName, InformalValueTypeName(val));
-        return false;
+                                   ClassName<T>(), methodName, InformalValueTypeName(val));
+        return nullptr;
     }
 
-    unwrappedResult.set(&obj->as<T>());
-    return true;
+    return &obj->as<T>();
 }
 
 template <class T>
-inline bool
+MOZ_MUST_USE T*
 UnwrapAndTypeCheckArgumentSlowPath(JSContext* cx,
                                    CallArgs& args,
                                    const char* methodName,
-                                   int argIndex,
-                                   MutableHandle<T*> unwrappedResult)
+                                   int argIndex)
 {
     Value val = args.get(argIndex);
     JSObject* obj = nullptr;
     if (val.isObject()) {
         obj = &val.toObject();
         if (IsWrapper(obj)) {
             obj = CheckedUnwrap(obj);
             if (!obj) {
                 ReportAccessDenied(cx);
-                return false;
+                return nullptr;
             }
         }
     }
 
     if (!obj || !obj->is<T>()) {
         ToCStringBuf cbuf;
         if (char* numStr = NumberToCString(cx, &cbuf, argIndex + 1, 10)) {
             JS_ReportErrorNumberLatin1(cx, GetErrorMessage, nullptr,
                                        JSMSG_WRONG_TYPE_ARG,
                                        numStr,
                                        methodName,
-                                       T::class_.name,
+                                       ClassName<T>(),
                                        InformalValueTypeName(val));
         }
-        return false;
+        return nullptr;
     }
 
-    unwrappedResult.set(&obj->as<T>());
-    return true;
+    return &obj->as<T>();
 }
 
 } // namespace detail
 
 /**
  * Remove all wrappers from `val` and try to downcast the result to `T`.
  *
- * DANGER: The value stored in `unwrappedResult` may not be same-compartment
- * with `cx`.
+ * DANGER: The result may not be same-compartment with `cx`.
  *
  * This throws a TypeError if the value isn't an object, cannot be unwrapped,
  * or isn't an instance of the expected type.
- *
- * Terminology note: The term "non-generic method" comes from ECMA-262. A
- * non-generic method is one that checks the type of `this`, typically because
- * it needs access to internal slots. Generic methods do not type-check. For
- * example, `Array.prototype.join` is generic; it can be applied to anything
- * with a `.length` property and elements.
  */
 template <class T>
-inline bool
-UnwrapThisForNonGenericMethod(JSContext* cx,
-                              HandleValue val,
-                              const char* className,
-                              const char* methodName,
-                              MutableHandle<T*> unwrappedResult)
+inline MOZ_MUST_USE T*
+UnwrapAndTypeCheckThis(JSContext* cx,
+                       CallArgs& args,
+                       const char* methodName)
 {
     static_assert(!std::is_convertible<T*, Wrapper*>::value,
                   "T can't be a Wrapper type; this function discards wrappers");
 
-    cx->check(val);
-    if (val.isObject() && val.toObject().is<T>()) {
-        unwrappedResult.set(&val.toObject().as<T>());
-        return true;
+    HandleValue thisv = args.thisv();
+    cx->check(thisv);
+    if (thisv.isObject() && thisv.toObject().is<T>()) {
+        return &thisv.toObject().as<T>();
     }
-    return detail::UnwrapThisSlowPath(cx, val, className, methodName, unwrappedResult);
+    return detail::UnwrapAndTypeCheckThisSlowPath<T>(cx, thisv, methodName);
 }
 
 /**
- * Extra signature so callers don't have to specify T explicitly.
+ * Remove all wrappers from `args[argIndex]` and try to downcast the result to
+ * class `T`.
+ *
+ * DANGER: The result may not be same-compartment with `cx`.
+ *
+ * This throws a TypeError if the specified argument is missing, isn't an
+ * object, cannot be unwrapped, or isn't an instance of the expected type.
  */
 template <class T>
-inline bool
-UnwrapThisForNonGenericMethod(JSContext* cx,
-                              HandleValue val,
-                              const char* className,
-                              const char* methodName,
-                              Rooted<T*>* out)
-{
-    return UnwrapThisForNonGenericMethod(cx, val, className, methodName, MutableHandle<T*>(out));
-}
-
-template <class T>
-inline bool
+inline MOZ_MUST_USE T*
 UnwrapAndTypeCheckArgument(JSContext* cx,
                            CallArgs& args,
                            const char* methodName,
-                           int argIndex,
-                           MutableHandle<T*> unwrappedResult)
+                           int argIndex)
 {
     static_assert(!std::is_convertible<T*, Wrapper*>::value,
                   "T can't be a Wrapper type; this function discards wrappers");
 
     Value val = args.get(argIndex);
     if (val.isObject() && val.toObject().is<T>()) {
-        unwrappedResult.set(&val.toObject().as<T>());
-        return true;
+        return &val.toObject().as<T>();
     }
-    return detail::UnwrapAndTypeCheckArgumentSlowPath(cx, args, methodName, argIndex,
-                                                      unwrappedResult);
-}
-
-/**
- * Extra signature so callers don't have to specify T explicitly.
- */
-template <class T>
-inline bool
-UnwrapAndTypeCheckArgument(JSContext* cx,
-                           CallArgs& args,
-                           const char* methodName,
-                           int argIndex,
-                           Rooted<T*>* unwrappedResult)
-{
-    return UnwrapAndTypeCheckArgument(cx, args, methodName, argIndex,
-                                      MutableHandle<T*>(unwrappedResult));
+    return detail::UnwrapAndTypeCheckArgumentSlowPath<T>(cx, args, methodName, argIndex);
 }
 
 /**
  * Unwrap a value of a known type.
  *
  * If `value` is an object of class T, this returns a pointer to that object.
  * If `value` is a wrapper for such an object, this tries to unwrap the object
  * and return a pointer to it. If access is denied, or `value` was a wrapper
@@ -278,16 +252,17 @@ UnwrapAndTypeCheckArgument(JSContext* cx
  * is known to have been initialized with an object of class T.
  */
 template <class T>
 MOZ_MUST_USE T*
 UnwrapAndDowncastValue(JSContext* cx, const Value& value)
 {
     static_assert(!std::is_convertible<T*, Wrapper*>::value,
                   "T can't be a Wrapper type; this function discards wrappers");
+
     JSObject* result = &value.toObject();
     if (IsProxy(result)) {
         if (JS_IsDeadWrapper(result)) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
             return nullptr;
         }
 
         // It would probably be OK to do an unchecked unwrap here, but we allow
@@ -311,58 +286,38 @@ UnwrapAndDowncastValue(JSContext* cx, co
  * sometimes store a cross-compartment wrapper in that slot. And since wrappers
  * can be nuked, that wrapper may become a dead object proxy.
  *
  * UnwrapInternalSlot() copes with the cross-compartment and dead object cases,
  * but not plain bugs where the slot hasn't been initialized or doesn't contain
  * the expected type of object. Call this only if the slot is certain to
  * contain either an instance of T, a wrapper for a T, or a dead object.
  *
- * cx and unwrappedObj are not required to be same-compartment.
+ * `cx` and `unwrappedObj` are not required to be same-compartment.
  *
- * DANGER: The result stored in `unwrappedResult` will not necessarily be
- * same-compartment with either cx or obj.
+ * DANGER: The result may not be same-compartment with either `cx` or `obj`.
  */
 template <class T>
-MOZ_MUST_USE bool
-UnwrapInternalSlot(JSContext* cx,
-                   Handle<NativeObject*> unwrappedObj,
-                   uint32_t slot,
-                   MutableHandle<T*> unwrappedResult)
+inline MOZ_MUST_USE T*
+UnwrapInternalSlot(JSContext* cx, Handle<NativeObject*> unwrappedObj, uint32_t slot)
 {
     static_assert(!std::is_convertible<T*, Wrapper*>::value,
                   "T can't be a Wrapper type; this function discards wrappers");
 
-    T* result = UnwrapAndDowncastValue<T>(cx, unwrappedObj->getFixedSlot(slot));
-    if (!result) {
-        return false;
-    }
-    unwrappedResult.set(result);
-    return true;
-}
-
-/**
- * Extra signature so callers don't have to specify T explicitly.
- */
-template <class T>
-inline MOZ_MUST_USE bool
-UnwrapInternalSlot(JSContext* cx,
-                   Handle<NativeObject*> obj,
-                   uint32_t slot,
-                   Rooted<T*>* unwrappedResult)
-{
-    return UnwrapInternalSlot(cx, obj, slot, MutableHandle<T*>(unwrappedResult));
+    return UnwrapAndDowncastValue<T>(cx, unwrappedObj->getFixedSlot(slot));
 }
 
 /**
  * Read a function slot that is known to point to a particular type of object.
  *
  * This is like UnwrapInternalSlot, but for extended function slots. Call this
  * only if the specified slot is known to have been initialized with an object
  * of class T or a wrapper for such an object.
+ *
+ * DANGER: The result may not be same-compartment with `cx`.
  */
 template <class T>
 MOZ_MUST_USE T*
 UnwrapCalleeSlot(JSContext* cx, CallArgs& args, size_t extendedSlot)
 {
     JSFunction& func = args.callee().as<JSFunction>();
     return UnwrapAndDowncastValue<T>(cx, func.getExtendedSlot(extendedSlot));
 }