Bug 1503718 - Part 2: Use the `unwrapped` prefix. Covers spec sections 3.5 to 3.9. r=tcampbell
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 16 Nov 2018 12:39:24 +0000
changeset 503185 c87bb95c2447c77beb835e7f2476fe1f066008c0
parent 503184 9fb1b0aeef48a633c30593c5cef6ee10bcfd7f6d
child 503186 1918d21eb212e46e671c7fdfd008406d4fee2ebb
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
bugs1503718
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 1503718 - Part 2: Use the `unwrapped` prefix. Covers spec sections 3.5 to 3.9. r=tcampbell ReportArgTypeError is replaced with a new helper function template, UnwrapAndTypeCheckArgument. The old function used the expression decompiler, but that seems unhelpful here; the new code uses InformalValueTypeName on the actual argument value. Differential Revision: https://phabricator.services.mozilla.com/D11684
js/src/builtin/Stream.cpp
js/src/js.msg
js/src/vm/Compartment-inl.h
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -180,28 +180,16 @@ PromiseRejectedWithPendingError(JSContex
         // worker is terminated. Propagate the uncatchable error. This will
         // typically kill off the calling asynchronous process: the caller
         // can't hook its continuation to the new rejected promise.
         return nullptr;
     }
     return PromiseObject::unforgeableReject(cx, exn);
 }
 
-static void
-ReportArgTypeError(JSContext* cx, const char* funName, const char* expectedType, HandleValue arg)
-{
-    UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr);
-    if (!bytes) {
-        return;
-    }
-
-    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NOT_EXPECTED_TYPE, funName,
-                             expectedType, bytes.get());
-}
-
 static MOZ_MUST_USE bool
 ReturnPromiseRejectedWithPendingError(JSContext* cx, const CallArgs& args)
 {
     JSObject* promise = PromiseRejectedWithPendingError(cx);
     if (!promise) {
         return false;
     }
 
@@ -543,18 +531,20 @@ ReadableStream::createStream(JSContext* 
     // Step 3: Set this.[[disturbed]] to false (implicit).
     // Step 4: Set this.[[readableStreamController]] to undefined (implicit).
     stream->initStateBits(Readable);
 
     return stream;
 }
 
 static MOZ_MUST_USE ReadableStreamDefaultController*
-CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
-                                      HandleValue underlyingSource, HandleValue size,
+CreateReadableStreamDefaultController(JSContext* cx,
+                                      Handle<ReadableStream*> stream,
+                                      HandleValue underlyingSource,
+                                      HandleValue size,
                                       HandleValue highWaterMarkVal);
 
 /**
  * Streams spec, 3.2.3., steps 1-4, 8.
  */
 ReadableStream*
 ReadableStream::createDefaultStream(JSContext* cx, HandleValue underlyingSource,
                                     HandleValue size, HandleValue highWaterMark,
@@ -1678,197 +1668,194 @@ ReadableStreamHasDefaultReader(JSContext
 }
 
 
 /*** 3.5. Class ReadableStreamDefaultReader ******************************************************/
 
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericInitialize(JSContext* cx,
                                       Handle<ReadableStreamReader*> reader,
-                                      Handle<ReadableStream*> stream);
+                                      Handle<ReadableStream*> unwrappedStream);
 
 /**
  * Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
  * Steps 2-4.
- *
- * Note: can operate on unwrapped ReadableStream instances from
- * another compartment. The returned object will always be created in the
- * current cx compartment.
  */
 static MOZ_MUST_USE ReadableStreamDefaultReader*
-CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream)
+CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> unwrappedStream)
 {
-    Rooted<ReadableStreamDefaultReader*> reader(cx);
-    reader = NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx);
+    Rooted<ReadableStreamDefaultReader*> reader(cx,
+        NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx));
     if (!reader) {
         return nullptr;
     }
 
     // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
     //         exception.
-    if (stream->locked()) {
+    if (unwrappedStream->locked()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
         return nullptr;
     }
 
     // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
-    if (!ReadableStreamReaderGenericInitialize(cx, reader, stream)) {
+    if (!ReadableStreamReaderGenericInitialize(cx, reader, unwrappedStream)) {
         return nullptr;
     }
 
     // Step 4: Set this.[[readRequests]] to a new empty List.
     if (!SetNewList(cx, reader, ReadableStreamReader::Slot_Requests)) {
         return nullptr;
     }
 
     return reader;
 }
 
 /**
  * Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
- *
- * Note: can handle ReadableStream instances from another compartment.
  */
 bool
 ReadableStreamDefaultReader::constructor(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultReader")) {
         return false;
     }
 
     // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
-    if (!IsMaybeWrapped<ReadableStream>(args.get(0))) {
-        ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream", args.get(0));
+    Rooted<ReadableStream*> unwrappedStream(cx);
+    if (!UnwrapAndTypeCheckArgument(cx, args, "ReadableStreamDefaultReader constructor", 0,
+                                    &unwrappedStream))
+    {
         return false;
     }
 
-    Rooted<ReadableStream*> stream(cx,
-                                   &CheckedUnwrap(&args.get(0).toObject())->as<ReadableStream>());
-
-    RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream));
+    RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, unwrappedStream));
     if (!reader) {
         return false;
     }
 
     args.rval().setObject(*reader);
     return true;
 }
 
-// Streams spec, 3.5.4.1 get closed
+/**
+ * Streams spec, 3.5.4.1 get closed
+ */
 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*> reader(cx);
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
     if (!UnwrapThisForNonGenericMethod(cx,
                                        args.thisv(),
                                        "ReadableStreamDefaultReader",
                                        "get closed",
-                                       &reader))
+                                       &unwrappedReader))
     {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: Return this.[[closedPromise]].
-    RootedObject closedPromise(cx, reader->closedPromise());
+    RootedObject closedPromise(cx, unwrappedReader->closedPromise());
     if (!cx->compartment()->wrap(cx, &closedPromise)) {
         return false;
     }
 
     args.rval().setObject(*closedPromise);
     return true;
 }
 
 static MOZ_MUST_USE JSObject*
-ReadableStreamReaderGenericCancel(JSContext* cx, Handle<ReadableStreamReader*> reader,
+ReadableStreamReaderGenericCancel(JSContext* cx,
+                                  Handle<ReadableStreamReader*> unwrappedReader,
                                   HandleValue reason);
 
-// Streams spec, 3.5.4.2. cancel ( reason )
+/**
+ * Streams spec, 3.5.4.2. cancel ( reason )
+ */
 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*> reader(cx);
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
     if (!UnwrapThisForNonGenericMethod(cx,
                                        args.thisv(),
                                        "ReadableStreamDefaultReader",
                                        "cancel",
-                                       &reader))
+                                       &unwrappedReader))
     {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
     //         rejected with a TypeError exception.
-    if (!reader->hasStream()) {
+    if (!unwrappedReader->hasStream()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMREADER_NOT_OWNED, "cancel");
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 3: Return ! ReadableStreamReaderGenericCancel(this, reason).
-    JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, reader, args.get(0));
+    JSObject* cancelPromise = ReadableStreamReaderGenericCancel(cx, unwrappedReader, args.get(0));
     if (!cancelPromise) {
         return false;
     }
     args.rval().setObject(*cancelPromise);
     return true;
 }
 
-// Streams spec, 3.5.4.3 read ( )
+/**
+ * Streams spec, 3.5.4.3 read ( )
+ */
 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*> reader(cx);
+    Rooted<ReadableStreamDefaultReader*> unwrappedReader(cx);
     if (!UnwrapThisForNonGenericMethod(cx,
                                        args.thisv(),
                                        "ReadableStreamDefaultReader",
                                        "read",
-                                       &reader))
+                                       &unwrappedReader))
     {
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
     //         rejected with a TypeError exception.
-    if (!reader->hasStream()) {
+    if (!unwrappedReader->hasStream()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                   JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
         return ReturnPromiseRejectedWithPendingError(cx, args);
     }
 
     // Step 3: Return ! ReadableStreamDefaultReaderRead(this).
-    JSObject* readPromise = ::ReadableStreamDefaultReaderRead(cx, reader);
+    JSObject* readPromise = ::ReadableStreamDefaultReaderRead(cx, unwrappedReader);
     if (!readPromise) {
         return false;
     }
     args.rval().setObject(*readPromise);
     return true;
 }
 
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericRelease(JSContext* cx, Handle<ReadableStreamReader*> reader);
 
 /**
  * Streams spec, 3.5.4.4. releaseLock ( )
- *
- * Note: can operate on unwrapped ReadableStreamDefaultReader instances from
- * another compartment.
  */
 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);
@@ -1896,17 +1883,22 @@ ReadableStreamDefaultReader_releaseLock(
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_READABLESTREAMREADER_NOT_EMPTY,
                                       "releaseLock");
             return false;
         }
     }
 
     // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
-    return ReadableStreamReaderGenericRelease(cx, reader);
+    if (!ReadableStreamReaderGenericRelease(cx, reader)) {
+        return false;
+    }
+
+    args.rval().setUndefined();
+    return true;
 }
 
 static const JSFunctionSpec ReadableStreamDefaultReader_methods[] = {
     JS_FN("cancel",         ReadableStreamDefaultReader_cancel,         1, 0),
     JS_FN("read",           ReadableStreamDefaultReader_read,           0, 0),
     JS_FN("releaseLock",    ReadableStreamDefaultReader_releaseLock,    0, 0),
     JS_FS_END
 };
@@ -1929,131 +1921,125 @@ CLASS_SPEC(ReadableStreamDefaultReader, 
 // Streams spec, 3.7.1. IsReadableStreamDefaultReader ( x )
 // Implemented via is<ReadableStreamDefaultReader>()
 
 // Streams spec, 3.7.2. IsReadableStreamBYOBReader ( x )
 // Implemented via is<ReadableStreamBYOBReader>()
 
 /**
  * Streams spec, 3.7.3. ReadableStreamReaderGenericCancel ( reader, reason )
- *
- * Note: can operate on unwrapped ReadableStream reader instances from
- * another compartment.
  */
 static MOZ_MUST_USE JSObject*
-ReadableStreamReaderGenericCancel(JSContext* cx, Handle<ReadableStreamReader*> reader,
+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*> stream(cx);
-    if (!UnwrapStreamFromReader(cx, reader, &stream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx);
+    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
         return nullptr;
     }
 
     // Step 3: Return ! ReadableStreamCancel(stream, reason).
-    return ::ReadableStreamCancel(cx, stream, reason);
+    return ::ReadableStreamCancel(cx, unwrappedStream, reason);
 }
 
 /**
  * Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
- *
- * Note: can operate on unwrapped ReadableStream reader instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamReaderGenericInitialize(JSContext* cx, Handle<ReadableStreamReader*> reader,
-                                      Handle<ReadableStream*> stream)
+                                      Handle<ReadableStream*> unwrappedStream)
 {
+    cx->check(reader);
+
     // Step 1: Set reader.[[ownerReadableStream]] to stream.
-    // Step 2: Set stream.[[reader]] to reader.
-    if (!IsObjectInContextCompartment(stream, cx)) {
-        RootedObject wrappedStream(cx, stream);
-        if (!cx->compartment()->wrap(cx, &wrappedStream)) {
+    {
+        RootedObject readerCompartmentStream(cx, unwrappedStream);
+        if (!cx->compartment()->wrap(cx, &readerCompartmentStream)) {
             return false;
         }
-        reader->setStream(wrappedStream);
-        AutoRealm ar(cx, stream);
-        RootedObject wrappedReader(cx, reader);
-        if (!cx->compartment()->wrap(cx, &wrappedReader)) {
+        reader->setStream(readerCompartmentStream);
+    }
+
+    // Step 2: Set stream.[[reader]] to reader.
+    {
+        AutoRealm ar(cx, unwrappedStream);
+        RootedObject streamCompartmentReader(cx, reader);
+        if (!cx->compartment()->wrap(cx, &streamCompartmentReader)) {
             return false;
         }
-        stream->setReader(wrappedReader);
-    } else {
-        reader->setStream(stream);
-        stream->setReader(reader);
+        unwrappedStream->setReader(streamCompartmentReader);
     }
 
     // Step 3: If stream.[[state]] is "readable",
     RootedObject promise(cx);
-    if (stream->readable()) {
+    if (unwrappedStream->readable()) {
         // Step a: Set reader.[[closedPromise]] to a new promise.
         promise = PromiseObject::createSkippingExecutor(cx);
-    } else if (stream->closed()) {
+    } else if (unwrappedStream->closed()) {
         // Step 4: Otherwise
         // Step a: If stream.[[state]] is "closed",
         // Step i: Set reader.[[closedPromise]] to a new promise resolved with
         //         undefined.
         promise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
     } else {
         // Step b: Otherwise,
         // Step i: Assert: stream.[[state]] is "errored".
-        MOZ_ASSERT(stream->errored());
+        MOZ_ASSERT(unwrappedStream->errored());
 
         // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
         //          stream.[[storedError]].
-        RootedValue storedError(cx, stream->storedError());
+        RootedValue storedError(cx, unwrappedStream->storedError());
         if (!cx->compartment()->wrap(cx, &storedError)) {
             return false;
         }
         promise = PromiseObject::unforgeableReject(cx, storedError);
     }
 
     if (!promise) {
         return false;
     }
 
     reader->setClosedPromise(promise);
     return true;
 }
 
 /**
  * Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader )
- *
- * Note: can operate on unwrapped ReadableStream reader instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
-ReadableStreamReaderGenericRelease(JSContext* cx, Handle<ReadableStreamReader*> reader)
+ReadableStreamReaderGenericRelease(JSContext* cx, Handle<ReadableStreamReader*> unwrappedReader)
 {
     // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
-    Rooted<ReadableStream*> stream(cx);
-    if (!UnwrapStreamFromReader(cx, reader, &stream)) {
+    Rooted<ReadableStream*> unwrappedStream(cx);
+    if (!UnwrapStreamFromReader(cx, unwrappedReader, &unwrappedStream)) {
         return false;
     }
 
     // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
-    MOZ_ASSERT(UnwrapReaderFromStreamNoThrow(stream) == 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.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAMREADER_RELEASED);
     RootedValue exn(cx);
     if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
         // 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 (stream->readable()) {
+    if (unwrappedStream->readable()) {
         Rooted<PromiseObject*> closedPromise(cx);
         if (!UnwrapInternalSlot(cx,
-                                reader,
+                                unwrappedReader,
                                 ReadableStreamReader::Slot_ClosedPromise,
                                 &closedPromise))
         {
             return false;
         }
 
         AutoRealm ar(cx, closedPromise);
         if (!cx->compartment()->wrap(cx, &exn)) {
@@ -2065,40 +2051,38 @@ ReadableStreamReaderGenericRelease(JSCon
     } else {
         // Step 4: Otherwise, set reader.[[closedPromise]] to a new promise rejected
         //         with a TypeError exception.
         RootedObject closedPromise(cx, PromiseObject::unforgeableReject(cx, exn));
         if (!closedPromise) {
             return false;
         }
 
-        AutoRealm ar(cx, reader);
+        AutoRealm ar(cx, unwrappedReader);
         if (!cx->compartment()->wrap(cx, &closedPromise)) {
             return false;
         }
-        reader->setClosedPromise(closedPromise);
+        unwrappedReader->setClosedPromise(closedPromise);
     }
 
     // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
-    stream->clearReader();
+    unwrappedStream->clearReader();
 
     // Step 6: Set reader.[[ownerReadableStream]] to undefined.
-    reader->clearStream();
+    unwrappedReader->clearStream();
 
     return true;
 }
 
 static MOZ_MUST_USE JSObject*
-ReadableStreamControllerPullSteps(JSContext* cx, Handle<ReadableStreamController*> controller);
+ReadableStreamControllerPullSteps(JSContext* cx,
+                                  Handle<ReadableStreamController*> unwrappedController);
 
 /**
  * Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
- *
- * Note: can operate on unwrapped ReadableStreamDefaultReader instances from
- * another compartment.
  */
 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);
@@ -2129,31 +2113,32 @@ ReadableStreamDefaultReaderRead(JSContex
         }
         return PromiseObject::unforgeableReject(cx, storedError);
     }
 
     // Step 6: Assert: stream.[[state]] is "readable".
     MOZ_ASSERT(unwrappedStream->readable());
 
     // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]]().
-    Rooted<ReadableStreamController*> unwrappedController(cx,
-        unwrappedStream->controller());
+    Rooted<ReadableStreamController*> unwrappedController(cx, unwrappedStream->controller());
     return ReadableStreamControllerPullSteps(cx, unwrappedController);
 }
 
 
 /*** 3.8. Class ReadableStreamDefaultController **************************************************/
 
 inline static MOZ_MUST_USE bool
 ReadableStreamControllerCallPullIfNeeded(JSContext* cx,
                                          Handle<ReadableStreamController*> unwrappedController);
 
-// Streams spec, 3.8.3, step 11.a.
-// and
-// Streams spec, 3.10.3, step 16.a.
+/**
+ * Streams spec, 3.8.3, step 11.a.
+ * and
+ * Streams spec, 3.10.3, step 16.a.
+ */
 static bool
 ControllerStartHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     Rooted<ReadableStreamController*> controller(cx);
     controller = TargetFromHandler<ReadableStreamController>(args.callee());
 
     // Step i: Set controller.[[started]] to true.
@@ -2171,41 +2156,46 @@ ControllerStartHandler(JSContext* cx, un
     if (!ReadableStreamControllerCallPullIfNeeded(cx, controller)) {
         return false;
     }
     args.rval().setUndefined();
     return true;
 }
 
 static MOZ_MUST_USE bool
-ReadableStreamControllerError(JSContext* cx, Handle<ReadableStreamController*> unwrappedController,
+ReadableStreamControllerError(JSContext* cx,
+                              Handle<ReadableStreamController*> unwrappedController,
                               HandleValue e);
 
-// Streams spec, 3.8.3, step 11.b.
-// and
-// Streams spec, 3.10.3, step 16.b.
+/**
+ * Streams spec, 3.8.3, step 11.b.
+ * and
+ * Streams spec, 3.10.3, step 16.b.
+ */
 static bool
 ControllerStartFailedHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ReadableStreamController*> controllerObj(cx);
-    controllerObj = TargetFromHandler<ReadableStreamController>(args.callee());
+    Rooted<ReadableStreamController*> controller(cx,
+        TargetFromHandler<ReadableStreamController>(args.callee()));
 
     // 3.8.3, Step 11.b.i:
     // Perform ! ReadableStreamDefaultControllerErrorIfNeeded(controller, r).
-    if (controllerObj->is<ReadableStreamDefaultController>()) {
-        Rooted<ReadableStreamDefaultController*> controller(cx);
-        controller = &controllerObj->as<ReadableStreamDefaultController>();
-        return ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, args.get(0));
+    if (controller->is<ReadableStreamDefaultController>()) {
+        Rooted<ReadableStreamDefaultController*> defaultController(cx,
+            &controller->as<ReadableStreamDefaultController>());
+        return ReadableStreamDefaultControllerErrorIfNeeded(cx, defaultController, args.get(0));
     }
 
     // 3.10.3, Step 16.b.i: If stream.[[state]] is "readable", perform
     //                      ! ReadableByteStreamControllerError(controller, r).
-    if (controllerObj->stream()->readable()) {
-        return ReadableStreamControllerError(cx, controllerObj, args.get(0));
+    if (controller->stream()->readable()) {
+        if (!ReadableStreamControllerError(cx, controller, args.get(0))) {
+            return false;
+        }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 static MOZ_MUST_USE bool
 ValidateAndNormalizeHighWaterMark(JSContext* cx,
@@ -2218,29 +2208,30 @@ ValidateAndNormalizeQueuingStrategy(JSCo
                                     HandleValue highWaterMarkVal,
                                     double* highWaterMark);
 
 /**
  * Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource,
  *                                                           size, highWaterMark )
  * Steps 3 - 11.
  *
- * Note: can NOT operate on unwrapped ReadableStream instances from
- * another compartment: ReadableStream controllers must be created in the same
- * compartment as the stream.
+ * Note: All arguments must be same-compartment with cx. ReadableStream
+ * controllers are always created in the same compartment as the stream.
  */
 static MOZ_MUST_USE ReadableStreamDefaultController*
-CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
-                                      HandleValue underlyingSource, HandleValue size,
+CreateReadableStreamDefaultController(JSContext* cx,
+                                      Handle<ReadableStream*> stream,
+                                      HandleValue underlyingSource,
+                                      HandleValue size,
                                       HandleValue highWaterMarkVal)
 {
     cx->check(stream, underlyingSource, size, highWaterMarkVal);
 
-    Rooted<ReadableStreamDefaultController*> controller(cx);
-    controller = NewBuiltinClassInstance<ReadableStreamDefaultController>(cx);
+    Rooted<ReadableStreamDefaultController*> controller(cx,
+        NewBuiltinClassInstance<ReadableStreamDefaultController>(cx));
     if (!controller) {
         return nullptr;
     }
 
     // Step 3: Set this.[[controlledReadableStream]] to stream.
     controller->setStream(stream);
 
     // Step 4: Set this.[[underlyingSource]] to underlyingSource.
@@ -2295,34 +2286,38 @@ CreateReadableStreamDefaultController(JS
 
     if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected)) {
         return nullptr;
     }
 
     return controller;
 }
 
-// Streams spec, 3.8.3.
-// new ReadableStreamDefaultController( stream, underlyingSource, size,
-//                                      highWaterMark )
+/**
+ * Streams spec, 3.8.3.
+ * new ReadableStreamDefaultController( stream, underlyingSource, size,
+ *                                      highWaterMark )
+ */
 bool
 ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp)
 {
     // Step 1: Throw a TypeError.
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_BOGUS_CONSTRUCTOR, "ReadableStreamDefaultController");
     return false;
 }
 
 static MOZ_MUST_USE double
 ReadableStreamControllerGetDesiredSizeUnchecked(ReadableStreamController* controller);
 
-// Streams spec, 3.8.4.1. get desiredSize
-// and
-// Streams spec, 3.10.4.2. get desiredSize
+/**
+ * Streams spec, 3.8.4.1. get desiredSize
+ * and
+ * 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,
@@ -2357,19 +2352,16 @@ ReadableStreamDefaultController_desiredS
 }
 
 static MOZ_MUST_USE bool
 ReadableStreamDefaultControllerClose(JSContext* cx,
                                      Handle<ReadableStreamDefaultController*> unwrappedController);
 
 /**
  * Unified implementation of step 2 of 3.8.4.2 and steps 2-3 of 3.10.4.3.
- *
- * Note: can operate on unwrapped ReadableStreamController instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
 VerifyControllerStateForClosing(JSContext* cx,
                                 Handle<ReadableStreamController*> unwrappedController)
 {
     // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
     if (unwrappedController->closeRequested()) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
@@ -2519,19 +2511,16 @@ CLASS_SPEC(ReadableStreamDefaultControll
            JS_NULL_CLASS_OPS);
 
 /**
  * Unified implementation of ReadableStream controllers' [[CancelSteps]] internal
  * methods.
  * Streams spec, 3.8.5.1. [[CancelSteps]] ( reason )
  * and
  * Streams spec, 3.10.5.1. [[CancelSteps]] ( reason )
- *
- * Note: can operate on unwrapped ReadableStreamController instances
- * from another compartment. |reason| must be in the current cx compartment.
  */
 static MOZ_MUST_USE JSObject*
 ReadableStreamControllerCancelSteps(JSContext* cx,
                                     Handle<ReadableStreamController*> unwrappedController,
                                     HandleValue reason)
 {
     AssertSameCompartment(cx, reason);
 
@@ -2614,19 +2603,16 @@ ReadableStreamControllerCancelSteps(JSCo
 
 inline static MOZ_MUST_USE bool
 DequeueValue(JSContext* cx,
              Handle<ReadableStreamController*> unwrappedContainer,
              MutableHandleValue chunk);
 
 /**
  * Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]]()
- *
- * Note: can operate on unwrapped ReadableStreamDefaultController instances
- * from another compartment.
  */
 static JSObject*
 ReadableStreamDefaultControllerPullSteps(JSContext* cx,
                                          Handle<ReadableStreamDefaultController*> unwrappedController)
 {
     // Step 1: Let stream be this.[[controlledReadableStream]].
     Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
@@ -2687,18 +2673,20 @@ ReadableStreamDefaultControllerPullSteps
 }
 
 
 /*** 3.9. Readable stream default controller abstract operations *********************************/
 
 // Streams spec, 3.9.1. IsReadableStreamDefaultController ( x )
 // Implemented via is<ReadableStreamDefaultController>()
 
-// Streams spec, 3.9.2 and 3.12.3. step 7:
-// Upon fulfillment of pullPromise,
+/**
+ * Streams spec, 3.9.2 and 3.12.3. step 7:
+ * Upon fulfillment of pullPromise,
+ */
 static bool
 ControllerPullHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     RootedValue controllerVal(cx, args.callee().as<JSFunction>().getExtendedSlot(0));
     Rooted<ReadableStreamController*> controller(cx);
     controller = ToUnwrapped<ReadableStreamController>(cx, controllerVal);
@@ -2719,18 +2707,20 @@ ControllerPullHandler(JSContext* cx, uns
             return false;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
-// Streams spec, 3.9.2 and 3.12.3. step 8:
-// Upon rejection of pullPromise with reason e,
+/**
+ * Streams spec, 3.9.2 and 3.12.3. step 8:
+ * Upon rejection of pullPromise with reason e,
+ */
 static bool
 ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     HandleValue e = args.get(0);
 
     RootedValue controllerVal(cx, args.callee().as<JSFunction>().getExtendedSlot(0));
     Rooted<ReadableStreamController*> controller(cx);
@@ -2747,85 +2737,86 @@ ControllerPullFailedHandler(JSContext* c
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
-ReadableStreamControllerShouldCallPull(ReadableStreamController* controller);
+ReadableStreamControllerShouldCallPull(ReadableStreamController* unwrappedController);
 
 static MOZ_MUST_USE double
-ReadableStreamControllerGetDesiredSizeUnchecked(ReadableStreamController* controller);
+ReadableStreamControllerGetDesiredSizeUnchecked(ReadableStreamController* unwrappedController);
 
 /**
  * Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
  * Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
- *
- * Note: can operate on unwrapped instances from other compartments for
- * |controller|.
  */
 inline static MOZ_MUST_USE bool
 ReadableStreamControllerCallPullIfNeeded(JSContext* cx,
-                                         Handle<ReadableStreamController*> controller)
+                                         Handle<ReadableStreamController*> unwrappedController)
 {
     // Step 1: Let shouldPull be
     //         ! ReadableByteStreamControllerShouldCallPull(controller).
-    bool shouldPull = ReadableStreamControllerShouldCallPull(controller);
+    bool shouldPull = ReadableStreamControllerShouldCallPull(unwrappedController);
 
     // Step 2: If shouldPull is false, return.
     if (!shouldPull) {
         return true;
     }
 
     // Step 3: If controller.[[pulling]] is true,
-    if (controller->pulling()) {
+    if (unwrappedController->pulling()) {
         // Step a: Set controller.[[pullAgain]] to true.
-        controller->setPullAgain();
+        unwrappedController->setPullAgain();
 
         // Step b: Return.
         return true;
     }
 
     // Step 4: Assert: controller.[[pullAgain]] is false.
-    MOZ_ASSERT(!controller->pullAgain());
+    MOZ_ASSERT(!unwrappedController->pullAgain());
 
     // Step 5: Set controller.[[pulling]] to true.
-    controller->setPulling();
+    unwrappedController->setPulling();
 
     // Step 6: Let pullPromise be
     //         ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller).
-    RootedObject wrappedController(cx, controller);
+    RootedObject wrappedController(cx, unwrappedController);
     if (!cx->compartment()->wrap(cx, &wrappedController)) {
         return false;
     }
     RootedValue controllerVal(cx, ObjectValue(*wrappedController));
-    RootedValue underlyingSource(cx, controller->underlyingSource());
+    RootedValue unwrappedUnderlyingSource(cx, unwrappedController->underlyingSource());
     RootedObject pullPromise(cx);
 
-    if (IsMaybeWrapped<TeeState>(underlyingSource)) {
-        Rooted<TeeState*> teeState(cx);
-        teeState = &UncheckedUnwrap(&underlyingSource.toObject())->as<TeeState>();
-        Rooted<ReadableStream*> stream(cx, controller->stream());
-        pullPromise = ReadableStreamTee_Pull(cx, teeState);
-    } else if (controller->hasExternalSource()) {
+    if (IsMaybeWrapped<TeeState>(unwrappedUnderlyingSource)) {
+        Rooted<TeeState*> unwrappedTeeState(cx);
+        unwrappedTeeState = &UncheckedUnwrap(&unwrappedUnderlyingSource.toObject())->as<TeeState>();
+        Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
+        pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState);
+    } else if (unwrappedController->hasExternalSource()) {
         {
-            AutoRealm ar(cx, controller);
-            Rooted<ReadableStream*> stream(cx, controller->stream());
-            void* source = underlyingSource.toPrivate();
-            double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+            AutoRealm ar(cx, unwrappedController);
+            Rooted<ReadableStream*> stream(cx, unwrappedController->stream());
+            void* source = unwrappedUnderlyingSource.toPrivate();
+            double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
             cx->runtime()->readableStreamDataRequestCallback(cx,
                                                              stream,
                                                              source,
                                                              stream->embeddingFlags(),
                                                              desiredSize);
         }
         pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
     } else {
+        RootedValue underlyingSource(cx, unwrappedUnderlyingSource);
+        if (!cx->compartment()->wrap(cx, &underlyingSource)) {
+            return false;
+        }
         pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal);
     }
     if (!pullPromise) {
         return false;
     }
 
     RootedObject onPullFulfilled(cx, NewHandler(cx, ControllerPullHandler, wrappedController));
     if (!onPullFulfilled) {
@@ -2840,236 +2831,223 @@ ReadableStreamControllerCallPullIfNeeded
     return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, onPullRejected);
 
     // Steps 7-8 implemented in functions above.
 }
 
 /**
  * Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller )
  * Streams spec, 3.12.25. ReadableByteStreamControllerShouldCallPull ( controller )
- *
- * Note: can operate on unwrapped ReadableStream controller instances from
- * another compartment.
  */
 static bool
-ReadableStreamControllerShouldCallPull(ReadableStreamController* controller)
+ReadableStreamControllerShouldCallPull(ReadableStreamController* unwrappedController)
 {
     // Step 1: Let stream be controller.[[controlledReadableStream]].
-    ReadableStream* stream = controller->stream();
+    ReadableStream* unwrappedStream = unwrappedController->stream();
 
     // Step 2: If stream.[[state]] is "closed" or stream.[[state]] is "errored",
     //         return false.
     // or, equivalently
     // Step 2: If stream.[[state]] is not "readable", return false.
-    if (!stream->readable()) {
+    if (!unwrappedStream->readable()) {
         return false;
     }
 
     // Step 3: If controller.[[closeRequested]] is true, return false.
-    if (controller->closeRequested()) {
+    if (unwrappedController->closeRequested()) {
         return false;
     }
 
     // Step 4: If controller.[[started]] is false, return false.
-    if (!controller->started()) {
+    if (!unwrappedController->started()) {
         return false;
     }
 
     // Step 5: If ! IsReadableStreamLocked(stream) is true and
     //         ! ReadableStreamGetNumReadRequests(stream) > 0, return true.
     // Steps 5-6 of 3.12.24 are equivalent in our implementation.
-    if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) {
+    if (unwrappedStream->locked() && ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
         return true;
     }
 
     // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller).
-    double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+    double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController);
 
     // Step 7: If desiredSize > 0, return true.
     // Step 8: Return false.
     // Steps 7-8 of 3.12.24 are equivalent in our implementation.
     return desiredSize > 0;
 }
 
 /**
  * Streams spec, 3.9.5. ReadableStreamDefaultControllerClose ( controller )
- *
- * Note: can operate on unwrapped ReadableStream controller instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultControllerClose(JSContext* cx,
-                                     Handle<ReadableStreamDefaultController*> controller)
+                                     Handle<ReadableStreamDefaultController*> unwrappedController)
 {
     // Step 1: Let stream be controller.[[controlledReadableStream]].
-    Rooted<ReadableStream*> stream(cx, controller->stream());
+    Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
     // Step 2: Assert: controller.[[closeRequested]] is false.
-    MOZ_ASSERT(!controller->closeRequested());
+    MOZ_ASSERT(!unwrappedController->closeRequested());
 
     // Step 3: Assert: stream.[[state]] is "readable".
-    MOZ_ASSERT(stream->readable());
+    MOZ_ASSERT(unwrappedStream->readable());
 
     // Step 4: Set controller.[[closeRequested]] to true.
-    controller->setCloseRequested();
+    unwrappedController->setCloseRequested();
 
     // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream).
-    RootedNativeObject queue(cx, controller->queue());
-    if (queue->getDenseInitializedLength() == 0) {
-        return ReadableStreamCloseInternal(cx, stream);
+    RootedNativeObject unwrappedQueue(cx, unwrappedController->queue());
+    if (unwrappedQueue->getDenseInitializedLength() == 0) {
+        return ReadableStreamCloseInternal(cx, unwrappedStream);
     }
 
     return true;
 }
 
 static MOZ_MUST_USE bool
-EnqueueValueWithSize(JSContext* cx, Handle<ReadableStreamController*> container, HandleValue value,
+EnqueueValueWithSize(JSContext* cx,
+                     Handle<ReadableStreamController*> unwrappedContainer,
+                     HandleValue value,
                      HandleValue sizeVal);
 
 /**
  * Streams spec, 3.9.6. ReadableStreamDefaultControllerEnqueue ( controller, chunk )
- *
- * Note: can operate on unwrapped instances from other compartments for
- * |controller|. |chunk| must be in the current cx compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultControllerEnqueue(JSContext* cx,
-                                       Handle<ReadableStreamDefaultController*> controller,
+                                       Handle<ReadableStreamDefaultController*> unwrappedController,
                                        HandleValue chunk)
 {
     AssertSameCompartment(cx, chunk);
 
     // Step 1: Let stream be controller.[[controlledReadableStream]].
-    Rooted<ReadableStream*> stream(cx, controller->stream());
+    Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
     // Step 2: Assert: controller.[[closeRequested]] is false.
-    MOZ_ASSERT(!controller->closeRequested());
+    MOZ_ASSERT(!unwrappedController->closeRequested());
 
     // Step 3: Assert: stream.[[state]] is "readable".
-    MOZ_ASSERT(stream->readable());
+    MOZ_ASSERT(unwrappedStream->readable());
 
     // Step 4: If ! IsReadableStreamLocked(stream) is true and
     //         ! ReadableStreamGetNumReadRequests(stream) > 0, perform
     //         ! ReadableStreamFulfillReadRequest(stream, chunk, false).
-    if (stream->locked() && ReadableStreamGetNumReadRequests(stream) > 0) {
-        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false)) {
+    if (unwrappedStream->locked() && ReadableStreamGetNumReadRequests(unwrappedStream) > 0) {
+        if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk, false)) {
             return false;
         }
     } else {
         // Step 5: Otherwise,
         // Step a: Let chunkSize be 1.
         RootedValue chunkSize(cx, NumberValue(1));
         bool success = true;
 
         // Step b: If controller.[[strategySize]] is not undefined,
-        RootedValue strategySize(cx, controller->strategySize());
+        RootedValue strategySize(cx, unwrappedController->strategySize());
         if (!strategySize.isUndefined()) {
             // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk).
             if (!cx->compartment()->wrap(cx, &strategySize)) {
                 return false;
             }
             success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
         }
 
         // Step c: Let enqueueResult be
         //         EnqueueValueWithSize(controller, chunk, chunkSize).
         if (success) {
-            success = EnqueueValueWithSize(cx, controller, chunk, chunkSize);
+            success = EnqueueValueWithSize(cx, unwrappedController, chunk, chunkSize);
         }
 
         if (!success) {
             // Step b.ii: If chunkSize is an abrupt completion,
             // and
             // Step d: If enqueueResult is an abrupt completion,
             RootedValue exn(cx);
             if (!cx->isExceptionPending() || !GetAndClearException(cx, &exn)) {
                 // Uncatchable error. Die immediately without erroring the
                 // stream.
                 return false;
             }
 
             // Step b.ii.1: Perform
             //         ! ReadableStreamDefaultControllerErrorIfNeeded(controller,
             //                                                        chunkSize.[[Value]]).
-            if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, exn)) {
+            if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, unwrappedController, exn)) {
                 return false;
             }
 
             // Step b.ii.2: Return chunkSize.
             cx->setPendingException(exn);
             return false;
         }
     }
 
     // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
     // Step 7: Return.
-    return ReadableStreamControllerCallPullIfNeeded(cx, controller);
+    return ReadableStreamControllerCallPullIfNeeded(cx, unwrappedController);
 }
 
 static MOZ_MUST_USE bool
 ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx,
-                                                  Handle<ReadableByteStreamController*> controller);
+                                                  Handle<ReadableByteStreamController*> unwrappedController);
 
 /**
  * Streams spec, 3.9.7. ReadableStreamDefaultControllerError ( controller, e )
  * Streams spec, 3.12.11. ReadableByteStreamControllerError ( controller, e )
- *
- * Note: can operate on unwrapped ReadableStream controller instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
-ReadableStreamControllerError(JSContext* cx, Handle<ReadableStreamController*> controller,
+ReadableStreamControllerError(JSContext* cx,
+                              Handle<ReadableStreamController*> unwrappedController,
                               HandleValue e)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
     AssertSameCompartment(cx, e);
 
     // Step 1: Let stream be controller.[[controlledReadableStream]].
-    Rooted<ReadableStream*> stream(cx, controller->stream());
+    Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
     // Step 2: Assert: stream.[[state]] is "readable".
-    MOZ_ASSERT(stream->readable());
+    MOZ_ASSERT(unwrappedStream->readable());
 
     // Step 3 of 3.12.10:
     // Perform ! ReadableByteStreamControllerClearPendingPullIntos(controller).
-    if (controller->is<ReadableByteStreamController>()) {
-        Rooted<ReadableByteStreamController*> byteStreamController(cx);
-        byteStreamController = &controller->as<ReadableByteStreamController>();
-        if (!ReadableByteStreamControllerClearPendingPullIntos(cx, byteStreamController)) {
+    if (unwrappedController->is<ReadableByteStreamController>()) {
+        Rooted<ReadableByteStreamController*> unwrappedByteStreamController(cx,
+            &unwrappedController->as<ReadableByteStreamController>());
+        if (!ReadableByteStreamControllerClearPendingPullIntos(cx, unwrappedByteStreamController)) {
             return false;
         }
     }
 
     // Step 3 (or 4): Perform ! ResetQueue(controller).
-    if (!ResetQueue(cx, controller)) {
+    if (!ResetQueue(cx, unwrappedController)) {
         return false;
     }
 
     // Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
-    return ReadableStreamErrorInternal(cx, stream, e);
+    return ReadableStreamErrorInternal(cx, unwrappedStream, e);
 }
 
 /**
  * Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow
- *
- * Note: can operate on unwrapped ReadableStreamDefaultController instances from
- * another compartment.
  */
 static MOZ_MUST_USE bool
 ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
-                                             Handle<ReadableStreamDefaultController*> controller,
+                                             Handle<ReadableStreamDefaultController*> unwrappedController,
                                              HandleValue e)
 {
     MOZ_ASSERT(!cx->isExceptionPending());
 
     // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable",
     //         perform ! ReadableStreamDefaultControllerError(controller, e).
-    Rooted<ReadableStream*> stream(cx, controller->stream());
-    if (stream->readable()) {
-        return ReadableStreamControllerError(cx, controller, e);
+    if (unwrappedController->stream()->readable()) {
+        return ReadableStreamControllerError(cx, unwrappedController, e);
     }
     return true;
 }
 
 /**
  * Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller )
  * Streams spec 3.12.14. ReadableByteStreamControllerGetDesiredSize ( controller )
  */
@@ -3937,29 +3915,28 @@ ResetQueue(JSContext* cx, Handle<Readabl
     return true;
 }
 
 
 /*** 6.3. Miscellaneous operations ***************************************************************/
 
 /**
  * Appends the given |obj| to the given list |container|'s list.
- *
- * Note: can operate on |container| and |obj| combinations from different
- * compartments, in which case |obj| is wrapped before storing it.
  */
 inline static MOZ_MUST_USE bool
-AppendToListAtSlot(JSContext* cx, HandleNativeObject container, uint32_t slot, HandleObject obj)
+AppendToListAtSlot(JSContext* cx,
+                   HandleNativeObject unwrappedContainer,
+                   uint32_t slot,
+                   HandleObject obj)
 {
-    RootedValue val(cx, container->getFixedSlot(slot));
-    RootedNativeObject list(cx, &val.toObject().as<NativeObject>());
-
-    val = ObjectValue(*obj);
+    RootedNativeObject list(cx,
+        &unwrappedContainer->getFixedSlot(slot).toObject().as<NativeObject>());
 
     AutoRealm ar(cx, list);
+    RootedValue val(cx, ObjectValue(*obj));
     if (!cx->compartment()->wrap(cx, &val)) {
         return false;
     }
     return AppendToList(cx, list, val);
 }
 
 
 /**
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -82,16 +82,17 @@ MSG_DEF(JSMSG_UTF8_CHAR_TOO_LARGE,     1
 MSG_DEF(JSMSG_MALFORMED_UTF8_CHAR,     1, JSEXN_TYPEERR, "malformed UTF-8 character sequence at offset {0}")
 MSG_DEF(JSMSG_BUILTIN_CTOR_NO_NEW,     1, JSEXN_TYPEERR, "calling a builtin {0} constructor without new is forbidden")
 MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE,      0, JSEXN_TYPEERR, "reduce of empty array with no initial value")
 MSG_DEF(JSMSG_UNEXPECTED_TYPE,         2, JSEXN_TYPEERR, "{0} is {1}")
 MSG_DEF(JSMSG_MISSING_FUN_ARG,         2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT,      1, JSEXN_TYPEERR, "{0} is not a non-null object")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT_NAME, 2, JSEXN_TYPEERR, "{0} must be an object, got {1}")
 MSG_DEF(JSMSG_NOT_NONNULL_OBJECT_ARG,  3, JSEXN_TYPEERR, "{0} argument of {1} must be an object, got {2}")
+MSG_DEF(JSMSG_WRONG_TYPE_ARG,          4, JSEXN_TYPEERR, "argument {0} to {1} must be an object of type {2}, got {3}")
 MSG_DEF(JSMSG_SET_NON_OBJECT_RECEIVER, 2, JSEXN_TYPEERR, "can't assign to property {1} on {0}: not an object")
 MSG_DEF(JSMSG_INVALID_DESCRIPTOR,      0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified")
 MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE,   1, JSEXN_TYPEERR, "{0}: Object is not extensible")
 MSG_DEF(JSMSG_CANT_DEFINE_PROP_OBJECT_NOT_EXTENSIBLE, 2, JSEXN_TYPEERR, "can't define property {1}: {0} is not extensible")
 MSG_DEF(JSMSG_CANT_REDEFINE_PROP,      1, JSEXN_TYPEERR, "can't redefine non-configurable property {0}")
 MSG_DEF(JSMSG_CANT_REDEFINE_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't redefine array length")
 MSG_DEF(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH, 0, JSEXN_TYPEERR, "can't define array index property past the end of an array with non-writable length")
 MSG_DEF(JSMSG_BAD_GET_SET_FIELD,       1, JSEXN_TYPEERR, "property descriptor's {0} field is neither undefined nor a function")
--- a/js/src/vm/Compartment-inl.h
+++ b/js/src/vm/Compartment-inl.h
@@ -8,16 +8,17 @@
 #define vm_Compartment_inl_h
 
 #include "vm/Compartment.h"
 
 #include <type_traits>
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "jsnum.h"
 #include "gc/Barrier.h"
 #include "gc/Marking.h"
 #include "js/Wrapper.h"
 #include "vm/Iteration.h"
 #include "vm/JSObject.h"
 
 #include "vm/JSContext-inl.h"
 
@@ -103,17 +104,17 @@ JS::Compartment::wrap(JSContext* cx, JS:
     vp.setObject(*obj);
     MOZ_ASSERT_IF(cacheResult, obj == cacheResult);
     return true;
 }
 
 namespace js {
 namespace detail {
 
-template<class T>
+template <class T>
 bool
 UnwrapThisSlowPath(JSContext* cx,
                    HandleValue val,
                    const char* className,
                    const char* methodName,
                    MutableHandle<T*> unwrappedResult)
 {
     if (!val.isObject()) {
@@ -136,16 +137,54 @@ UnwrapThisSlowPath(JSContext* cx,
                                    className, methodName, InformalValueTypeName(val));
         return false;
     }
 
     unwrappedResult.set(&obj->as<T>());
     return true;
 }
 
+template <class T>
+inline bool
+UnwrapAndTypeCheckArgumentSlowPath(JSContext* cx,
+                                   CallArgs& args,
+                                   const char* methodName,
+                                   int argIndex,
+                                   MutableHandle<T*> unwrappedResult)
+{
+    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;
+            }
+        }
+    }
+
+    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,
+                                       InformalValueTypeName(val));
+        }
+        return false;
+    }
+
+    unwrappedResult.set(&obj->as<T>());
+    return true;
+}
+
 } // 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`.
  *
@@ -153,17 +192,17 @@ UnwrapThisSlowPath(JSContext* cx,
  * 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>
+template <class T>
 inline bool
 UnwrapThisForNonGenericMethod(JSContext* cx,
                               HandleValue val,
                               const char* className,
                               const char* methodName,
                               MutableHandle<T*> unwrappedResult)
 {
     static_assert(!std::is_convertible<T*, Wrapper*>::value,
@@ -175,27 +214,62 @@ UnwrapThisForNonGenericMethod(JSContext*
         return true;
     }
     return detail::UnwrapThisSlowPath(cx, val, className, methodName, unwrappedResult);
 }
 
 /**
  * Extra signature so callers don't have to specify T explicitly.
  */
-template<class T>
+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
+UnwrapAndTypeCheckArgument(JSContext* cx,
+                           CallArgs& args,
+                           const char* methodName,
+                           int argIndex,
+                           MutableHandle<T*> unwrappedResult)
+{
+    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 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));
+}
+
 /**
  * Read a private slot that is known to point to a particular type of object.
  *
  * Some internal slots specified in various standards effectively have static
  * types. For example, the [[ownerReadableStream]] slot of a stream reader is
  * guaranteed to be a ReadableStream. However, because of compartments, we
  * sometimes store a cross-compartment wrapper in that slot. And since wrappers
  * can be nuked, that wrapper may become a dead object proxy.