Created
November 13, 2018 14:37
-
-
Save jorendorff/caac74098713ceea170907f862f3a78a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
changeset: 511721:b9b50b3dec73 | |
tag: tip | |
parent: 511719:8584c117d229 | |
user: Jason Orendorff <[email protected]> | |
date: Mon Nov 12 18:08:38 2018 -0600 | |
files: js/public/Stream.h js/src/builtin/Stream.cpp js/src/builtin/Stream.h js/src/jsapi-tests/testReadableStream.cpp js/src/vm/Runtime.cpp js/src/vm/Runtime.h | |
description: | |
No bug yet - Abstract base class for external underlying sources. r?baku,jwalden | |
diff --git a/js/public/Stream.h b/js/public/Stream.h | |
--- a/js/public/Stream.h | |
+++ b/js/public/Stream.h | |
@@ -16,32 +16,31 @@ | |
* | |
* Embeddings can create ReadableStreams that read from custom C++ data | |
* sources. Such streams are always byte streams: the chunks they produce are | |
* typed arrays (and they will support ReadableStreamBYOBReader once we have | |
* it). | |
* | |
* When creating an "external readable stream" using | |
* JS::NewReadableExternalSourceStreamObject, an underlying source can be | |
- * passed to be stored on the stream. The underlying source is treated as an | |
- * opaque void* pointer by the JS engine: it's purely meant as a reference to | |
- * be used by the embedding to identify whatever actual source it uses to | |
- * supply data for the stream. | |
+ * passed to be stored on the stream. The underlying source is ???treated as an | |
+ * ???opaque void*??? pointer by the JS engine: it's purely meant as a | |
+ * reference to be used by the embedding to identify whatever actual source it | |
+ * uses to supply data for the stream. | |
* | |
* External readable streams are optimized to allow the embedding to interact | |
* with them with a minimum of overhead: chunks aren't enqueued as individual | |
* typed arrays; instead, the embedding only updates the amount of data | |
* available using ReadableStreamUpdateDataAvailableFromSource. When JS | |
- * requests data from a reader, WriteIntoReadRequestBufferCallback is invoked, | |
- * asking the embedding to write data directly into the buffer we're about to | |
- * hand to JS. | |
+ * requests data from a reader, writeIntoReadRequestBuffer is invoked, asking the | |
+ * embedding to write data directly into the buffer we're about to hand to JS. | |
* | |
* Additionally, ReadableStreamGetExternalUnderlyingSource can be used to get | |
- * the void* pointer to the underlying source. This locks the stream until it | |
- * is released again using JS::ReadableStreamReleaseExternalUnderlyingSource. | |
+ * the pointer to the underlying source. This locks the stream until it is | |
+ * released again using JS::ReadableStreamReleaseExternalUnderlyingSource. | |
* | |
* Embeddings can use this to optimize away the JS `ReadableStream` overhead | |
* when an embedding-defined C++ stream is passed to an embedding-defined C++ | |
* consumer. For example, consider a ServiceWorker piping a `fetch` Response | |
* body to a TextDecoder. Instead of copying chunks of data into JS typed array | |
* buffers and creating a Promise per chunk, only to immediately resolve the | |
* Promises and read the data out again, the embedding can directly feed the | |
* incoming data to the TextDecoder. | |
@@ -54,117 +53,93 @@ | |
#include "js/TypeDecls.h" | |
namespace JS { | |
/** | |
* ## Readable stream callbacks | |
* | |
- * Compartment safety: All callbacks (except Finalize) receive `cx` and | |
+ * Compartment safety: All methods (except Finalize) receive `cx` and | |
* `stream` arguments. SpiderMonkey enters the realm of the stream object | |
- * before invoking these callbacks, so `stream` is never a wrapper. Other | |
+ * before invoking these methods, so `stream` is never a wrapper. Other | |
* arguments may be wrappers. | |
*/ | |
+class ReadableStreamUnderlyingSource { | |
+ public: | |
+ virtual ~ReadableStreamUnderlyingSource() {} | |
-/** | |
- * Invoked whenever a reader desires more data from a ReadableStream's | |
- * embedding-provided underlying source. | |
- * | |
- * The given |desiredSize| is the absolute size, not a delta from the previous | |
- * desired size. | |
- */ | |
-typedef void | |
-(* RequestReadableStreamDataCallback)(JSContext* cx, HandleObject stream, | |
- void* underlyingSource, size_t desiredSize); | |
+ /** | |
+ * Invoked whenever a reader desires more data from a ReadableStream's | |
+ * embedding-provided underlying source. | |
+ * | |
+ * The given |desiredSize| is the absolute size, not a delta from the previous | |
+ * desired size. | |
+ */ | |
+ virtual void requestData(JSContext* cx, HandleObject stream, size_t desiredSize) = 0; | |
-/** | |
- * Invoked to cause the embedding to fill the given |buffer| with data from | |
- * the given embedding-provided underlying source. | |
- * | |
- * This can only happen after the embedding has updated the amount of data | |
- * available using JS::ReadableStreamUpdateDataAvailableFromSource. If at | |
- * least one read request is pending when | |
- * JS::ReadableStreamUpdateDataAvailableFromSource is called, | |
- * the WriteIntoReadRequestBufferCallback is invoked immediately from under | |
- * the call to JS::WriteIntoReadRequestBufferCallback. If not, it is invoked | |
- * if and when a new read request is made. | |
- * | |
- * Note: This callback *must not cause GC*, because that could potentially | |
- * invalidate the |buffer| pointer. | |
- */ | |
-typedef void | |
-(* WriteIntoReadRequestBufferCallback)(JSContext* cx, HandleObject stream, | |
- void* underlyingSource, void* buffer, size_t length, | |
- size_t* bytesWritten); | |
+ /** | |
+ * Invoked to cause the embedding to fill the given |buffer| with data from | |
+ * the given embedding-provided underlying source. | |
+ * | |
+ * This can only happen after the embedding has updated the amount of data | |
+ * available using JS::ReadableStreamUpdateDataAvailableFromSource. If at | |
+ * least one read request is pending when | |
+ * JS::ReadableStreamUpdateDataAvailableFromSource is called, the | |
+ * writeIntoReadRequestBuffer method is invoked immediately from under the | |
+ * call to JS::ReadableStreamUpdateDataAvailableFromSource. If not, it is | |
+ * invoked if and when a new read request is made. | |
+ * | |
+ * Note: This method *must not cause GC*, because that could potentially | |
+ * invalidate the |buffer| pointer. | |
+ */ | |
+ virtual void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream, | |
+ void* buffer, size_t length, | |
+ size_t* bytesWritten) = 0; | |
-/** | |
- * Invoked in reaction to the ReadableStream being canceled to allow the | |
- * embedding to free the underlying source. | |
- * | |
- * This is equivalent to calling |cancel| on non-external underlying sources | |
- * provided to the ReadableStream constructor in JavaScript. | |
- * | |
- * The given |reason| is the JS::Value that was passed as an argument to | |
- * ReadableStream#cancel(). | |
- * | |
- * The returned JS::Value will be used to resolve the Promise returned by | |
- * ReadableStream#cancel(). | |
- */ | |
-typedef Value | |
-(* CancelReadableStreamCallback)(JSContext* cx, HandleObject stream, | |
- void* underlyingSource, HandleValue reason); | |
- | |
-/** | |
- * Invoked in reaction to a ReadableStream with an embedding-provided | |
- * underlying source being closed. | |
- */ | |
-typedef void | |
-(* ReadableStreamClosedCallback)(JSContext* cx, HandleObject stream, void* underlyingSource); | |
+ /** | |
+ * Invoked in reaction to the ReadableStream being canceled to allow the | |
+ * embedding to free the underlying source. | |
+ * | |
+ * This is equivalent to calling |cancel| on non-external underlying sources | |
+ * provided to the ReadableStream constructor in JavaScript. | |
+ * | |
+ * The given |reason| is the JS::Value that was passed as an argument to | |
+ * ReadableStream#cancel(). | |
+ * | |
+ * The returned JS::Value will be used to resolve the Promise returned by | |
+ * ReadableStream#cancel(). | |
+ */ | |
+ virtual Value cancel(JSContext* cx, HandleObject stream, HandleValue reason) = 0; | |
-/** | |
- * Invoked in reaction to a ReadableStream with an embedding-provided | |
- * underlying source being errored with the | |
- * given reason. | |
- */ | |
-typedef void | |
-(* ReadableStreamErroredCallback)(JSContext* cx, HandleObject stream, void* underlyingSource, | |
- HandleValue reason); | |
+ /** | |
+ * Invoked in reaction to a ReadableStream with an embedding-provided | |
+ * underlying source being closed. | |
+ */ | |
+ virtual void onClosed(JSContext* cx, HandleObject stream) = 0; | |
+ | |
+ /** | |
+ * Invoked in reaction to a ReadableStream with an embedding-provided | |
+ * underlying source being errored with the given reason. | |
+ */ | |
+ virtual void onErrored(JSContext* cx, HandleObject stream, HandleValue reason) = 0; | |
-/** | |
- * Invoked in reaction to a ReadableStream with an embedding-provided | |
- * underlying source being finalized. Only the underlying source is passed | |
- * as an argument, while the ReadableStream itself is not to prevent the | |
- * embedding from operating on a JSObject that might not be in a valid state | |
- * anymore. | |
- * | |
- * Note: the ReadableStream might be finalized on a background thread. That | |
- * means this callback might be invoked from an arbitrary thread, which the | |
- * embedding must be able to handle. | |
- */ | |
-typedef void | |
-(* ReadableStreamFinalizeCallback)(void* underlyingSource); | |
- | |
-/** | |
- * Sets runtime-wide callbacks to use for interacting with embedding-provided | |
- * hooks for operating on ReadableStream instances. | |
- * | |
- * See the documentation for the individual callback types for details. | |
- */ | |
-extern JS_PUBLIC_API(void) | |
-SetReadableStreamCallbacks(JSContext* cx, | |
- RequestReadableStreamDataCallback dataRequestCallback, | |
- WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback, | |
- CancelReadableStreamCallback cancelCallback, | |
- ReadableStreamClosedCallback closedCallback, | |
- ReadableStreamErroredCallback erroredCallback, | |
- ReadableStreamFinalizeCallback finalizeCallback); | |
- | |
-extern JS_PUBLIC_API(bool) | |
-HasReadableStreamCallbacks(JSContext* cx); | |
+ /** | |
+ * Invoked in reaction to a ReadableStream with an embedding-provided | |
+ * underlying source being finalized. Only the underlying source is passed | |
+ * as an argument, while the ReadableStream itself is not to prevent the | |
+ * embedding from operating on a JSObject that might not be in a valid state | |
+ * anymore. | |
+ * | |
+ * Note: the ReadableStream might be finalized on a background thread. That | |
+ * means this method might be invoked from an arbitrary thread, which the | |
+ * embedding must be able to handle. | |
+ */ | |
+ virtual void finalize() = 0; | |
+}; | |
/** | |
* Returns a new instance of the ReadableStream builtin class in the current | |
* compartment, configured as a default stream. | |
* If a |proto| is passed, that gets set as the instance's [[Prototype]] | |
* instead of the original value of |ReadableStream.prototype|. | |
*/ | |
extern JS_PUBLIC_API(JSObject*) | |
@@ -174,29 +149,28 @@ NewReadableDefaultStreamObject(JSContext | |
/** | |
* Returns a new instance of the ReadableStream builtin class in the current | |
* compartment, with the right slot layout. If a |proto| is passed, that gets | |
* set as the instance's [[Prototype]] instead of the original value of | |
* |ReadableStream.prototype|. | |
* | |
* The instance is optimized for operating as a byte stream backed by an | |
- * embedding-provided underlying source, using the callbacks set via | |
- * |JS::SetReadableStreamCallbacks|. | |
+ * embedding-provided underlying source, using the virtual methods of | |
+ * |underlyingSource| as callbacks. | |
* | |
* Note: the embedding is responsible for ensuring that the pointer to the | |
* underlying source stays valid as long as the stream can be read from. | |
* The underlying source can be freed if the tree is canceled or errored. | |
* It can also be freed if the stream is destroyed. The embedding is notified | |
- * of that using ReadableStreamFinalizeCallback. | |
- * | |
- * Note: |underlyingSource| must have an even address. | |
+ * of that using the finalize() method. | |
*/ | |
extern JS_PUBLIC_API(JSObject*) | |
-NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource, | |
+NewReadableExternalSourceStreamObject(JSContext* cx, | |
+ ReadableStreamUnderlyingSource* underlyingSource, | |
HandleObject proto = nullptr); | |
/** | |
* Returns the embedding-provided underlying source of the given |stream|. | |
* | |
* Can be used to optimize operations if both the underlying source and the | |
* intended sink are embedding-provided. In that case it might be | |
* preferrable to pipe data directly from source to sink without interacting | |
@@ -214,17 +188,18 @@ NewReadableExternalSourceStreamObject(JS | |
* have a Promise to resolve/reject, which a reader provides. | |
* | |
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper | |
* for one. | |
* | |
* Asserts that the stream has an embedding-provided underlying source. | |
*/ | |
extern JS_PUBLIC_API(bool) | |
-ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, void** source); | |
+ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, | |
+ ReadableStreamUnderlyingSource** source); | |
/** | |
* Releases the embedding-provided underlying source of the given |stream|, | |
* returning the stream into an unlocked state. | |
* | |
* Asserts that the stream was locked through | |
* ReadableStreamGetExternalUnderlyingSource. | |
* | |
@@ -237,17 +212,17 @@ extern JS_PUBLIC_API(bool) | |
ReadableStreamReleaseExternalUnderlyingSource(JSContext* cx, HandleObject stream); | |
/** | |
* Update the amount of data available at the underlying source of the given | |
* |stream|. | |
* | |
* Can only be used for streams with an embedding-provided underlying source. | |
* The JS engine will use the given value to satisfy read requests for the | |
- * stream by invoking the JS::WriteIntoReadRequestBuffer callback. | |
+ * stream by invoking the writeIntoReadRequestBuffer method. | |
* | |
* Asserts that |stream| is a ReadableStream object or an unwrappable wrapper | |
* for one. | |
*/ | |
extern JS_PUBLIC_API(bool) | |
ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, HandleObject stream, | |
uint32_t availableData); | |
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp | |
--- a/js/src/builtin/Stream.cpp | |
+++ b/js/src/builtin/Stream.cpp | |
@@ -562,29 +562,30 @@ ReadableStream::createDefaultStream(JSCo | |
return nullptr; | |
} | |
stream->setController(controller); | |
return stream; | |
} | |
static MOZ_MUST_USE ReadableByteStreamController* | |
CreateExternalReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream, | |
- void* underlyingSource); | |
+ JS::ReadableStreamUnderlyingSource* source); | |
ReadableStream* | |
-ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource, | |
+ReadableStream::createExternalSourceStream(JSContext* cx, | |
+ JS::ReadableStreamUnderlyingSource* source, | |
HandleObject proto /* = nullptr */) | |
{ | |
Rooted<ReadableStream*> stream(cx, createStream(cx, proto)); | |
if (!stream) { | |
return nullptr; | |
} | |
Rooted<ReadableStreamController*> controller(cx); | |
- controller = CreateExternalReadableByteStreamController(cx, stream, underlyingSource); | |
+ controller = CreateExternalReadableByteStreamController(cx, stream, source); | |
if (!controller) { | |
return nullptr; | |
} | |
stream->setController(controller); | |
return stream; | |
} | |
@@ -1449,24 +1450,21 @@ ReadableStreamCloseInternal(JSContext* c | |
RootedObject closedPromise(cx, unwrappedReader->closedPromise()); | |
if (!cx->compartment()->wrap(cx, &closedPromise)) { | |
return false; | |
} | |
if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue)) { | |
return false; | |
} | |
- if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource && | |
- cx->runtime()->readableStreamClosedCallback) | |
- { | |
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) { | |
// Make sure we're in the stream's compartment. | |
AutoRealm ar(cx, unwrappedStream); | |
- ReadableStreamController* controller = unwrappedStream->controller(); | |
- void* source = controller->underlyingSource().toPrivate(); | |
- cx->runtime()->readableStreamClosedCallback(cx, unwrappedStream, source); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedStream->controller()->externalSource(); | |
+ source->onClosed(cx, unwrappedStream); | |
} | |
return true; | |
} | |
/** | |
* Streams spec, 3.4.6. ReadableStreamError ( stream, e ) | |
*/ | |
@@ -1540,32 +1538,29 @@ ReadableStreamErrorInternal(JSContext* c | |
RootedObject closedPromise(cx, unwrappedReader->closedPromise()); | |
if (!cx->compartment()->wrap(cx, &closedPromise)) { | |
return false; | |
} | |
if (!RejectPromise(cx, closedPromise, e)) { | |
return false; | |
} | |
- if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource && | |
- cx->runtime()->readableStreamErroredCallback) | |
+ if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) | |
{ | |
// Make sure we're in the stream's compartment. | |
AutoRealm ar(cx, unwrappedStream); | |
- ReadableStreamController* controller = unwrappedStream->controller(); | |
- void* source = controller->underlyingSource().toPrivate(); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedStream->controller()->externalSource(); | |
// Ensure that the embedding doesn't have to deal with | |
// mixed-compartment arguments to the callback. | |
RootedValue error(cx, e); | |
if (!cx->compartment()->wrap(cx, &error)) { | |
return false; | |
} | |
- | |
- cx->runtime()->readableStreamErroredCallback(cx, unwrappedStream, source, error); | |
+ source->onErrored(cx, unwrappedStream, error); | |
} | |
return true; | |
} | |
/** | |
* Streams spec, 3.4.7. ReadableStreamFulfillReadIntoRequest( stream, chunk, done ) | |
* Streams spec, 3.4.8. ReadableStreamFulfillReadRequest ( stream, chunk, done ) | |
@@ -2556,25 +2551,25 @@ ReadableStreamControllerCancelSteps(JSCo | |
return ReadableStreamTee_Cancel(cx, unwrappedteeState, unwrappedDefaultController, | |
reason); | |
} | |
if (unwrappedController->hasExternalSource()) { | |
RootedValue rval(cx); | |
{ | |
AutoRealm ar(cx, unwrappedController); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource(); | |
Rooted<ReadableStream*> stream(cx, unwrappedController->stream()); | |
- void* source = unwrappedUnderlyingSource.toPrivate(); | |
RootedValue wrappedReason(cx, reason); | |
if (!cx->compartment()->wrap(cx, &wrappedReason)) { | |
return nullptr; | |
} | |
cx->check(stream, wrappedReason); | |
- rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source, wrappedReason); | |
+ rval = source->cancel(cx, stream, wrappedReason); | |
} | |
if (!cx->compartment()->wrap(cx, &rval)) { | |
return nullptr; | |
} | |
return PromiseObject::unforgeableResolve(cx, rval); | |
} | |
@@ -2778,20 +2773,20 @@ ReadableStreamControllerCallPullIfNeeded | |
MOZ_ASSERT(unwrappedUnderlyingSource.toObject().is<TeeState>(), | |
"tee streams and controllers are always same-compartment with the TeeState object"); | |
Rooted<TeeState*> unwrappedTeeState(cx, | |
&unwrappedUnderlyingSource.toObject().as<TeeState>()); | |
pullPromise = ReadableStreamTee_Pull(cx, unwrappedTeeState); | |
} else if (unwrappedController->hasExternalSource()) { | |
{ | |
AutoRealm ar(cx, unwrappedController); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource(); | |
Rooted<ReadableStream*> stream(cx, unwrappedController->stream()); | |
- void* source = unwrappedUnderlyingSource.toPrivate(); | |
double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(unwrappedController); | |
- cx->runtime()->readableStreamDataRequestCallback(cx, stream, source, desiredSize); | |
+ source->requestData(cx, stream, 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); | |
@@ -3183,29 +3178,29 @@ ReadableByteStreamController::constructo | |
/** | |
* Version of the ReadableByteStreamConstructor that's specialized for | |
* handling external, embedding-provided, underlying sources. | |
*/ | |
static MOZ_MUST_USE ReadableByteStreamController* | |
CreateExternalReadableByteStreamController(JSContext* cx, | |
Handle<ReadableStream*> stream, | |
- void* underlyingSource) | |
+ JS::ReadableStreamUnderlyingSource* source) | |
{ | |
Rooted<ReadableByteStreamController*> controller(cx, | |
NewBuiltinClassInstance<ReadableByteStreamController>(cx)); | |
if (!controller) { | |
return nullptr; | |
} | |
// Step 3: Set this.[[controlledReadableStream]] to stream. | |
controller->setStream(stream); | |
// Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource. | |
- controller->setUnderlyingSource(PrivateValue(underlyingSource)); | |
+ controller->setUnderlyingSource(PrivateValue(source)); | |
// Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false. | |
controller->setFlags(ReadableStreamController::Flag_ExternalSource); | |
// Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this). | |
// Omitted. | |
// Step 7: Perform ! ResetQueue(this). | |
@@ -3271,18 +3266,17 @@ ReadableByteStreamControllerFinalize(Fre | |
if (controller.getFixedSlot(ReadableStreamController::Slot_Flags).isUndefined()) { | |
return; | |
} | |
if (!controller.hasExternalSource()) { | |
return; | |
} | |
- void* underlyingSource = controller.underlyingSource().toPrivate(); | |
- obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource); | |
+ controller.externalSource()->finalize(); | |
} | |
static const ClassOps ReadableByteStreamControllerClassOps = { | |
nullptr, /* addProperty */ | |
nullptr, /* delProperty */ | |
nullptr, /* enumerate */ | |
nullptr, /* newEnumerate */ | |
nullptr, /* resolve */ | |
@@ -3328,34 +3322,33 @@ ReadableByteStreamControllerPullSteps(JS | |
double queueTotalSize = unwrappedController->queueTotalSize(); | |
if (queueTotalSize > 0) { | |
// Step 3.a: Assert: ! ReadableStreamGetNumReadRequests(_stream_) is 0. | |
MOZ_ASSERT(ReadableStreamGetNumReadRequests(unwrappedStream) == 0); | |
RootedObject view(cx); | |
if (unwrappedStream->mode() == JS::ReadableStreamMode::ExternalSource) { | |
- void* underlyingSource = unwrappedController->underlyingSource().toPrivate(); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource(); | |
view = JS_NewUint8Array(cx, queueTotalSize); | |
if (!view) { | |
return nullptr; | |
} | |
size_t bytesWritten; | |
{ | |
AutoRealm ar(cx, unwrappedStream); | |
JS::AutoSuppressGCAnalysis suppressGC(cx); | |
JS::AutoCheckCannotGC noGC; | |
bool dummy; | |
void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC); | |
- auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback; | |
- MOZ_ASSERT(cb); | |
- cb(cx, unwrappedStream, underlyingSource, buffer, queueTotalSize, &bytesWritten); | |
+ source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer, queueTotalSize, | |
+ &bytesWritten); | |
} | |
queueTotalSize = queueTotalSize - bytesWritten; | |
} else { | |
// Step 3.b: Let entry be the first element of this.[[queue]]. | |
// Step 3.c: Remove entry from this.[[queue]], shifting all other elements | |
// downward (so that the second becomes the first, and so on). | |
RootedNativeObject unwrappedQueue(cx, unwrappedController->queue()); | |
@@ -4002,55 +3995,16 @@ JS_FRIEND_API(JSObject*) | |
js::UnwrapReadableStream(JSObject* obj) | |
{ | |
if (JSObject* unwrapped = CheckedUnwrap(obj)) { | |
return unwrapped->is<ReadableStream>() ? unwrapped : nullptr; | |
} | |
return nullptr; | |
} | |
-extern JS_PUBLIC_API(void) | |
-JS::SetReadableStreamCallbacks(JSContext* cx, | |
- JS::RequestReadableStreamDataCallback dataRequestCallback, | |
- JS::WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback, | |
- JS::CancelReadableStreamCallback cancelCallback, | |
- JS::ReadableStreamClosedCallback closedCallback, | |
- JS::ReadableStreamErroredCallback erroredCallback, | |
- JS::ReadableStreamFinalizeCallback finalizeCallback) | |
-{ | |
- MOZ_ASSERT(dataRequestCallback); | |
- MOZ_ASSERT(writeIntoReadRequestCallback); | |
- MOZ_ASSERT(cancelCallback); | |
- MOZ_ASSERT(closedCallback); | |
- MOZ_ASSERT(erroredCallback); | |
- MOZ_ASSERT(finalizeCallback); | |
- | |
- JSRuntime* rt = cx->runtime(); | |
- | |
- MOZ_ASSERT(!rt->readableStreamDataRequestCallback); | |
- MOZ_ASSERT(!rt->readableStreamWriteIntoReadRequestCallback); | |
- MOZ_ASSERT(!rt->readableStreamCancelCallback); | |
- MOZ_ASSERT(!rt->readableStreamClosedCallback); | |
- MOZ_ASSERT(!rt->readableStreamErroredCallback); | |
- MOZ_ASSERT(!rt->readableStreamFinalizeCallback); | |
- | |
- rt->readableStreamDataRequestCallback = dataRequestCallback; | |
- rt->readableStreamWriteIntoReadRequestCallback = writeIntoReadRequestCallback; | |
- rt->readableStreamCancelCallback = cancelCallback; | |
- rt->readableStreamClosedCallback = closedCallback; | |
- rt->readableStreamErroredCallback = erroredCallback; | |
- rt->readableStreamFinalizeCallback = finalizeCallback; | |
-} | |
- | |
-JS_PUBLIC_API(bool) | |
-JS::HasReadableStreamCallbacks(JSContext* cx) | |
-{ | |
- return cx->runtime()->readableStreamDataRequestCallback; | |
-} | |
- | |
JS_PUBLIC_API(JSObject*) | |
JS::NewReadableDefaultStreamObject(JSContext* cx, | |
JS::HandleObject underlyingSource /* = nullptr */, | |
JS::HandleFunction size /* = nullptr */, | |
double highWaterMark /* = 1 */, | |
JS::HandleObject proto /* = nullptr */) | |
{ | |
MOZ_ASSERT(!cx->zone()->isAtomsZone()); | |
@@ -4068,34 +4022,26 @@ JS::NewReadableDefaultStreamObject(JSCon | |
RootedValue sourceVal(cx, ObjectValue(*source)); | |
RootedValue sizeVal(cx, size ? ObjectValue(*size) : UndefinedValue()); | |
RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark)); | |
return ReadableStream::createDefaultStream(cx, sourceVal, sizeVal, highWaterMarkVal, proto); | |
} | |
JS_PUBLIC_API(JSObject*) | |
JS::NewReadableExternalSourceStreamObject(JSContext* cx, | |
- void* underlyingSource, | |
+ JS::ReadableStreamUnderlyingSource* underlyingSource, | |
HandleObject proto /* = nullptr */) | |
{ | |
MOZ_ASSERT(!cx->zone()->isAtomsZone()); | |
AssertHeapIsIdle(); | |
CHECK_THREAD(cx); | |
+ MOZ_ASSERT(underlyingSource); | |
MOZ_ASSERT((uintptr_t(underlyingSource) & 1) == 0, | |
"external underlying source pointers must be aligned"); | |
cx->check(proto); | |
-#ifdef DEBUG | |
- JSRuntime* rt = cx->runtime(); | |
- MOZ_ASSERT(rt->readableStreamDataRequestCallback); | |
- MOZ_ASSERT(rt->readableStreamWriteIntoReadRequestCallback); | |
- MOZ_ASSERT(rt->readableStreamCancelCallback); | |
- MOZ_ASSERT(rt->readableStreamClosedCallback); | |
- MOZ_ASSERT(rt->readableStreamErroredCallback); | |
- MOZ_ASSERT(rt->readableStreamFinalizeCallback); | |
-#endif // DEBUG | |
return ReadableStream::createExternalSourceStream(cx, underlyingSource, proto); | |
} | |
JS_PUBLIC_API(bool) | |
JS::IsReadableStream(JSObject* obj) | |
{ | |
return obj->canUnwrapAs<ReadableStream>(); | |
@@ -4196,17 +4142,18 @@ JS::ReadableStreamGetReader(JSContext* c | |
} | |
JSObject* result = CreateReadableStreamDefaultReader(cx, unwrappedStream); | |
MOZ_ASSERT_IF(result, IsObjectInContextCompartment(result, cx)); | |
return result; | |
} | |
JS_PUBLIC_API(bool) | |
-JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source) | |
+JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, | |
+ JS::ReadableStreamUnderlyingSource** source) | |
{ | |
AssertHeapIsIdle(); | |
CHECK_THREAD(cx); | |
Rooted<ReadableStream*> unwrappedStream(cx, APIToUnwrapped<ReadableStream>(cx, streamObj)); | |
if (!unwrappedStream) { | |
return false; | |
} | |
@@ -4220,17 +4167,17 @@ JS::ReadableStreamGetExternalUnderlyingS | |
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, | |
JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, | |
"ReadableStreamGetExternalUnderlyingSource"); | |
return false; | |
} | |
auto unwrappedController = &unwrappedStream->controller()->as<ReadableByteStreamController>(); | |
unwrappedController->setSourceLocked(); | |
- *source = unwrappedController->underlyingSource().toPrivate(); | |
+ *source = unwrappedController->externalSource(); | |
return true; | |
} | |
JS_PUBLIC_API(bool) | |
JS::ReadableStreamReleaseExternalUnderlyingSource(JSContext* cx, HandleObject streamObj) | |
{ | |
ReadableStream* unwrappedStream = APIToUnwrapped<ReadableStream>(cx, streamObj); | |
if (!unwrappedStream) { | |
@@ -4316,28 +4263,27 @@ JS::ReadableStreamUpdateDataAvailableFro | |
if (!viewObj) { | |
return false; | |
} | |
Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>()); | |
if (!transferredView) { | |
return false; | |
} | |
- void* underlyingSource = unwrappedController->underlyingSource().toPrivate(); | |
+ JS::ReadableStreamUnderlyingSource* source = unwrappedController->externalSource(); | |
size_t bytesWritten; | |
{ | |
AutoRealm ar(cx, unwrappedStream); | |
JS::AutoSuppressGCAnalysis suppressGC(cx); | |
JS::AutoCheckCannotGC noGC; | |
bool dummy; | |
void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC); | |
- auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback; | |
- MOZ_ASSERT(cb); | |
- cb(cx, unwrappedStream, underlyingSource, buffer, availableData, &bytesWritten); | |
+ source->writeIntoReadRequestBuffer(cx, unwrappedStream, buffer, availableData, | |
+ &bytesWritten); | |
} | |
// Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false). | |
RootedValue chunk(cx, ObjectValue(*transferredView)); | |
if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, unwrappedStream, chunk, false)) { | |
return false; | |
} | |
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h | |
--- a/js/src/builtin/Stream.h | |
+++ b/js/src/builtin/Stream.h | |
@@ -95,17 +95,18 @@ class ReadableStream : public NativeObje | |
JS::ReadableStreamMode mode() const; | |
bool locked() const; | |
public: | |
static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource, | |
HandleValue size, HandleValue highWaterMark, | |
HandleObject proto = nullptr); | |
- static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource, | |
+ static ReadableStream* createExternalSourceStream(JSContext* cx, | |
+ JS::ReadableStreamUnderlyingSource* source, | |
HandleObject proto = nullptr); | |
private: | |
static MOZ_MUST_USE ReadableStream* createStream(JSContext* cx, HandleObject proto = nullptr); | |
public: | |
static bool constructor(JSContext* cx, unsigned argc, Value* vp); | |
static const ClassSpec classSpec_; | |
@@ -239,16 +240,23 @@ class ReadableStreamController : public | |
ReadableStream* stream() const { | |
return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>(); | |
} | |
void setStream(ReadableStream* stream) { setFixedSlot(Slot_Stream, ObjectValue(*stream)); } | |
Value underlyingSource() const { return getFixedSlot(Slot_UnderlyingSource); } | |
void setUnderlyingSource(const Value& underlyingSource) { | |
setFixedSlot(Slot_UnderlyingSource, underlyingSource); | |
} | |
+ JS::ReadableStreamUnderlyingSource* externalSource() const { | |
+ static_assert(alignof(JS::ReadableStreamUnderlyingSource) >= 2, | |
+ "External underling sources are stored as PrivateValues, " | |
+ "so they must have even addresses"); | |
+ MOZ_ASSERT(hasExternalSource()); | |
+ return static_cast<JS::ReadableStreamUnderlyingSource*>(underlyingSource().toPrivate()); | |
+ } | |
double strategyHWM() const { return getFixedSlot(Slot_StrategyHWM).toNumber(); } | |
void setStrategyHWM(double highWaterMark) { | |
setFixedSlot(Slot_StrategyHWM, NumberValue(highWaterMark)); | |
} | |
uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); } | |
void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); } | |
void addFlags(uint32_t flags) { setFlags(this->flags() | flags); } | |
void removeFlags(uint32_t flags) { setFlags(this->flags() & ~flags); } | |
@@ -301,18 +309,19 @@ class ReadableStreamDefaultController : | |
class ReadableByteStreamController : public ReadableStreamController | |
{ | |
public: | |
/** | |
* Memory layout for ReadableByteStreamControllers, starting after the | |
* slots shared among all types of controllers. | |
* | |
- * PendingPullIntos is guaranteed to be in the same compartment as the | |
- * controller, but might contain wrappers for objects from other compartments. | |
+ * PendingPullIntos is guaranteed to be in the same compartment as the | |
+ * controller, but might contain wrappers for objects from other | |
+ * compartments. | |
* | |
* AutoAllocateSize is a primitive (numeric) value. | |
*/ | |
enum Slots { | |
Slot_BYOBRequest = ReadableStreamController::SlotCount, | |
Slot_PendingPullIntos, | |
Slot_AutoAllocateSize, | |
SlotCount | |
diff --git a/js/src/jsapi-tests/testReadableStream.cpp b/js/src/jsapi-tests/testReadableStream.cpp | |
--- a/js/src/jsapi-tests/testReadableStream.cpp | |
+++ b/js/src/jsapi-tests/testReadableStream.cpp | |
@@ -6,123 +6,109 @@ | |
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
#include "jsapi.h" | |
#include "jsfriendapi.h" | |
#include "jsapi-tests/tests.h" | |
using namespace JS; | |
-struct StubExternalUnderlyingSource { | |
- void* buffer; | |
+char testBufferData[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | |
+ | |
+struct StubExternalUnderlyingSource : public JS::ReadableStreamUnderlyingSource { | |
+ void* buffer = testBufferData; | |
+ bool dataRequestCBCalled = false; | |
+ bool writeIntoRequestBufferCBCalled = false; | |
+ bool cancelStreamCBCalled = false; | |
+ Value cancelStreamReason; | |
+ bool streamClosedCBCalled = false; | |
+ Value streamClosedReason; | |
+ bool streamErroredCBCalled = false; | |
+ Value streamErroredReason; | |
+ bool finalizeStreamCBCalled = false; | |
+ void* finalizedStreamUnderlyingSource; | |
+ | |
+ static StubExternalUnderlyingSource instance; | |
+ | |
+ void requestData(JSContext* cx, HandleObject stream, size_t desiredSize) override { | |
+ js::AssertSameCompartment(cx, stream); | |
+ MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup"); | |
+ dataRequestCBCalled = true; | |
+ } | |
+ | |
+ void writeIntoReadRequestBuffer(JSContext* cx, HandleObject stream, | |
+ void* buffer, size_t length, size_t* bytesWritten) override | |
+ { | |
+ js::AssertSameCompartment(cx, stream); | |
+ MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup"); | |
+ writeIntoRequestBufferCBCalled = true; | |
+ | |
+ MOZ_ASSERT(this == &StubExternalUnderlyingSource::instance); | |
+ MOZ_ASSERT(StubExternalUnderlyingSource::instance.buffer == testBufferData); | |
+ MOZ_ASSERT(length <= sizeof(testBufferData)); | |
+ memcpy(buffer, testBufferData, length); | |
+ *bytesWritten = length; | |
+ } | |
+ | |
+ Value cancel(JSContext* cx, HandleObject stream, HandleValue reason) override { | |
+ js::AssertSameCompartment(cx, stream); | |
+ js::AssertSameCompartment(cx, reason); | |
+ MOZ_ASSERT(!cancelStreamCBCalled, "Invalid test setup"); | |
+ cancelStreamCBCalled = true; | |
+ cancelStreamReason = reason; | |
+ return reason; | |
+ } | |
+ | |
+ void onClosed(JSContext* cx, HandleObject stream) override { | |
+ js::AssertSameCompartment(cx, stream); | |
+ MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup"); | |
+ streamClosedCBCalled = true; | |
+ } | |
+ | |
+ void onErrored(JSContext* cx, HandleObject stream, HandleValue reason) override { | |
+ js::AssertSameCompartment(cx, stream); | |
+ js::AssertSameCompartment(cx, reason); | |
+ MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup"); | |
+ streamErroredCBCalled = true; | |
+ streamErroredReason = reason; | |
+ } | |
+ | |
+ void finalize() override { | |
+ MOZ_ASSERT(!finalizeStreamCBCalled, "Invalid test setup"); | |
+ finalizeStreamCBCalled = true; | |
+ finalizedStreamUnderlyingSource = this; | |
+ } | |
+ | |
+ void reset() { | |
+ dataRequestCBCalled = false; | |
+ writeIntoRequestBufferCBCalled = false; | |
+ cancelStreamReason = UndefinedValue(); | |
+ cancelStreamCBCalled = false; | |
+ streamClosedCBCalled = false; | |
+ streamErroredCBCalled = false; | |
+ finalizeStreamCBCalled = false; | |
+ } | |
}; | |
-char testBufferData[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; | |
+StubExternalUnderlyingSource StubExternalUnderlyingSource::instance; | |
-StubExternalUnderlyingSource stubExternalUnderlyingSource = { | |
- testBufferData | |
-}; | |
static_assert(MOZ_ALIGNOF(StubExternalUnderlyingSource) > 1, | |
"UnderlyingSource pointers must not have the low bit set"); | |
static JSObject* | |
NewDefaultStream(JSContext* cx, HandleObject source = nullptr, HandleFunction size = nullptr, | |
double highWaterMark = 1, HandleObject proto = nullptr) | |
{ | |
RootedObject stream(cx, NewReadableDefaultStreamObject(cx, source, size, highWaterMark, | |
proto)); | |
MOZ_ASSERT_IF(stream, IsReadableStream(stream)); | |
return stream; | |
} | |
-static bool dataRequestCBCalled = false; | |
-static void | |
-DataRequestCB(JSContext* cx, HandleObject stream, void* underlyingSource, | |
- size_t desiredSize) | |
-{ | |
- js::AssertSameCompartment(cx, stream); | |
- MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup"); | |
- dataRequestCBCalled = true; | |
-} | |
- | |
-static bool writeIntoRequestBufferCBCalled = false; | |
-static void | |
-WriteIntoRequestBufferCB(JSContext* cx, HandleObject stream, void* underlyingSource, | |
- void* buffer, size_t length, size_t* bytesWritten) | |
-{ | |
- js::AssertSameCompartment(cx, stream); | |
- MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup"); | |
- writeIntoRequestBufferCBCalled = true; | |
- | |
- MOZ_ASSERT(underlyingSource == &stubExternalUnderlyingSource); | |
- MOZ_ASSERT(stubExternalUnderlyingSource.buffer == testBufferData); | |
- MOZ_ASSERT(length <= sizeof(testBufferData)); | |
- memcpy(buffer, testBufferData, length); | |
- *bytesWritten = length; | |
-} | |
- | |
-static bool cancelStreamCBCalled = false; | |
-static Value cancelStreamReason; | |
-static Value | |
-CancelStreamCB(JSContext* cx, HandleObject stream, void* underlyingSource, | |
- HandleValue reason) | |
-{ | |
- js::AssertSameCompartment(cx, stream); | |
- js::AssertSameCompartment(cx, reason); | |
- MOZ_ASSERT(!cancelStreamCBCalled, "Invalid test setup"); | |
- cancelStreamCBCalled = true; | |
- cancelStreamReason = reason; | |
- return reason; | |
-} | |
- | |
-static bool streamClosedCBCalled = false; | |
-static Value streamClosedReason; | |
-static void | |
-StreamClosedCB(JSContext* cx, HandleObject stream, void* underlyingSource) | |
-{ | |
- js::AssertSameCompartment(cx, stream); | |
- MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup"); | |
- streamClosedCBCalled = true; | |
-} | |
- | |
-static bool streamErroredCBCalled = false; | |
-static Value streamErroredReason; | |
-static void | |
-StreamErroredCB(JSContext* cx, HandleObject stream, void* underlyingSource, | |
- HandleValue reason) | |
-{ | |
- js::AssertSameCompartment(cx, stream); | |
- js::AssertSameCompartment(cx, reason); | |
- MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup"); | |
- streamErroredCBCalled = true; | |
- streamErroredReason = reason; | |
-} | |
- | |
-static bool finalizeStreamCBCalled = false; | |
-static void* finalizedStreamUnderlyingSource; | |
-static void | |
-FinalizeStreamCB(void* underlyingSource) | |
-{ | |
- MOZ_ASSERT(!finalizeStreamCBCalled, "Invalid test setup"); | |
- finalizeStreamCBCalled = true; | |
- finalizedStreamUnderlyingSource = underlyingSource; | |
-} | |
- | |
-static void | |
-ResetCallbacks() | |
-{ | |
- dataRequestCBCalled = false; | |
- writeIntoRequestBufferCBCalled = false; | |
- cancelStreamReason = UndefinedValue(); | |
- cancelStreamCBCalled = false; | |
- streamClosedCBCalled = false; | |
- streamErroredCBCalled = false; | |
- finalizeStreamCBCalled = false; | |
-} | |
static bool | |
GetIterResult(JSContext* cx, HandleObject promise, MutableHandleValue value, bool* done) | |
{ | |
RootedObject iterResult(cx, &GetPromiseResult(promise).toObject()); | |
bool found; | |
if (!JS_HasProperty(cx, iterResult, "value", &found)) { | |
@@ -260,19 +246,16 @@ BEGIN_FIXTURE_TEST(StreamTestFixture, | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamDefaultReaderRead) | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamDefaultReaderClose) | |
{ | |
- SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB, | |
- &CancelStreamCB, &StreamClosedCB, &StreamErroredCB, | |
- &FinalizeStreamCB); | |
RootedObject stream(cx, NewDefaultStream(cx)); | |
CHECK(stream); | |
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); | |
CHECK(reader); | |
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); | |
CHECK(request); | |
CHECK(IsPromiseObject(request)); | |
@@ -283,30 +266,27 @@ BEGIN_FIXTURE_TEST(StreamTestFixture, | |
bool done; | |
RootedValue value(cx); | |
CHECK(GetPromiseState(request) == PromiseState::Fulfilled); | |
CHECK(GetIterResult(cx, request, &value, &done)); | |
CHECK(value.isUndefined()); | |
CHECK(done); | |
// The callbacks are only invoked for external streams. | |
- CHECK(!streamClosedCBCalled); | |
+ CHECK(!StubExternalUnderlyingSource::instance.streamClosedCBCalled); | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamDefaultReaderClose) | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamDefaultReaderError) | |
{ | |
- ResetCallbacks(); | |
- SetReadableStreamCallbacks(cx, &DataRequestCB, &WriteIntoRequestBufferCB, | |
- &CancelStreamCB, &StreamClosedCB, &StreamErroredCB, | |
- &FinalizeStreamCB); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
RootedObject stream(cx, NewDefaultStream(cx)); | |
CHECK(stream); | |
RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default)); | |
CHECK(reader); | |
RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader)); | |
CHECK(request); | |
CHECK(IsPromiseObject(request)); | |
@@ -322,97 +302,81 @@ BEGIN_FIXTURE_TEST(StreamTestFixture, | |
CHECK(ReadableStreamError(cx, stream, error)); | |
CHECK(GetPromiseState(request) == PromiseState::Rejected); | |
RootedValue reason(cx, GetPromiseResult(request)); | |
CHECK(reason.isInt32()); | |
CHECK(reason.toInt32() == 42); | |
// The callbacks are only invoked for external streams. | |
- CHECK(!streamErroredCBCalled); | |
+ CHECK(!StubExternalUnderlyingSource::instance.streamErroredCBCalled); | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamDefaultReaderError) | |
static JSObject* | |
-NewExternalSourceStream(JSContext* cx, void* underlyingSource, | |
- RequestReadableStreamDataCallback dataRequestCallback, | |
- WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback, | |
- CancelReadableStreamCallback cancelCallback, | |
- ReadableStreamClosedCallback closedCallback, | |
- ReadableStreamErroredCallback erroredCallback, | |
- ReadableStreamFinalizeCallback finalizeCallback) | |
+NewExternalSourceStream(JSContext* cx, ReadableStreamUnderlyingSource* source) | |
{ | |
- SetReadableStreamCallbacks(cx, dataRequestCallback, writeIntoReadRequestCallback, | |
- cancelCallback, closedCallback, erroredCallback, | |
- finalizeCallback); | |
- RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, underlyingSource)); | |
+ RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, source)); | |
MOZ_ASSERT_IF(stream, IsReadableStream(stream)); | |
return stream; | |
} | |
static JSObject* | |
NewExternalSourceStream(JSContext* cx) | |
{ | |
- return NewExternalSourceStream(cx, | |
- &stubExternalUnderlyingSource, | |
- &DataRequestCB, | |
- &WriteIntoRequestBufferCB, | |
- &CancelStreamCB, | |
- &StreamClosedCB, | |
- &StreamErroredCB, | |
- &FinalizeStreamCB); | |
+ return NewExternalSourceStream(cx, &StubExternalUnderlyingSource::instance); | |
} | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_CreateReadableByteStreamWithExternalSource) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
RootedObject stream(cx, NewExternalSourceStream(cx)); | |
CHECK(stream); | |
ReadableStreamMode mode; | |
CHECK(ReadableStreamGetMode(cx, stream, &mode)); | |
CHECK(mode == ReadableStreamMode::ExternalSource); | |
- void* underlyingSource; | |
+ ReadableStreamUnderlyingSource* underlyingSource; | |
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); | |
- CHECK(underlyingSource == &stubExternalUnderlyingSource); | |
+ CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); | |
bool locked; | |
CHECK(ReadableStreamIsLocked(cx, stream, &locked)); | |
CHECK(locked); | |
CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_CreateReadableByteStreamWithExternalSource) | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ExternalSourceCancel) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
RootedObject stream(cx, NewExternalSourceStream(cx)); | |
CHECK(stream); | |
RootedValue reason(cx, Int32Value(42)); | |
CHECK(ReadableStreamCancel(cx, stream, reason)); | |
- CHECK(cancelStreamCBCalled); | |
- CHECK(cancelStreamReason == reason); | |
+ CHECK(StubExternalUnderlyingSource::instance.cancelStreamCBCalled); | |
+ CHECK(StubExternalUnderlyingSource::instance.cancelStreamReason == reason); | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ExternalSourceCancel) | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ExternalSourceGetReader) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
RootedObject stream(cx, NewExternalSourceStream(cx)); | |
CHECK(stream); | |
RootedValue streamVal(cx, ObjectValue(*stream)); | |
CHECK(JS_SetProperty(cx, global, "stream", streamVal)); | |
RootedValue rval(cx); | |
EVAL("stream.getReader()", &rval); | |
@@ -469,51 +433,51 @@ struct ReadFromExternalSourceFixture : p | |
return true; | |
} | |
bool readWithoutDataAvailable(CompartmentMode compartmentMode, | |
const char* evalSrc, | |
const char* evalSrc2, | |
uint32_t writtenLength) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
definePrint(); | |
// Create the stream. | |
RootedObject streamGlobal(cx); | |
RootedObject stream(cx); // can be a wrapper | |
CHECK(createExternalSourceStream(compartmentMode, &streamGlobal, &stream)); | |
js::RunJobs(cx); | |
// GetExternalUnderlyingSource locks the stream. | |
- void* underlyingSource; | |
+ ReadableStreamUnderlyingSource* underlyingSource; | |
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); | |
- CHECK(underlyingSource == &stubExternalUnderlyingSource); | |
+ CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); | |
bool locked; | |
CHECK(ReadableStreamIsLocked(cx, stream, &locked)); | |
CHECK(locked); | |
CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); | |
// Run caller-supplied JS code to read from the stream. | |
RootedValue streamVal(cx, ObjectValue(*stream)); | |
CHECK(JS_SetProperty(cx, global, "stream", streamVal)); | |
RootedValue rval(cx); | |
EVAL(evalSrc, &rval); | |
- CHECK(dataRequestCBCalled); | |
- CHECK(!writeIntoRequestBufferCBCalled); | |
+ CHECK(StubExternalUnderlyingSource::instance.dataRequestCBCalled); | |
+ CHECK(!StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); | |
CHECK(rval.isObject()); | |
RootedObject unwrappedPromise(cx, js::CheckedUnwrap(&rval.toObject())); | |
CHECK(unwrappedPromise); | |
CHECK(IsPromiseObject(unwrappedPromise)); | |
CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Pending); | |
// Stream in some data; this resolves the read() result promise. | |
size_t length = sizeof(testBufferData); | |
CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, length)); | |
- CHECK(writeIntoRequestBufferCBCalled); | |
+ CHECK(StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); | |
CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Fulfilled); | |
RootedObject chunk(cx); | |
{ | |
JSAutoRealm ar(cx, unwrappedPromise); | |
RootedValue iterVal(cx); | |
bool done; | |
if (!GetIterResult(cx, unwrappedPromise, &iterVal, &done)) { | |
return false; | |
@@ -527,56 +491,56 @@ struct ReadFromExternalSourceFixture : p | |
{ | |
JS::AutoCheckCannotGC noGC(cx); | |
bool dummy; | |
void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC); | |
CHECK(!memcmp(buffer, testBufferData, writtenLength)); | |
} | |
// Check the callbacks fired by calling read() again. | |
- dataRequestCBCalled = false; | |
- writeIntoRequestBufferCBCalled = false; | |
+ StubExternalUnderlyingSource::instance.dataRequestCBCalled = false; | |
+ StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled = false; | |
EVAL(evalSrc2, &rval); | |
- CHECK(dataRequestCBCalled); | |
- CHECK(!writeIntoRequestBufferCBCalled); | |
+ CHECK(StubExternalUnderlyingSource::instance.dataRequestCBCalled); | |
+ CHECK(!StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); | |
return true; | |
} | |
bool readWithDataAvailable(CompartmentMode compartmentMode, | |
const char* evalSrc, | |
uint32_t writtenLength) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
definePrint(); | |
// Create a stream. | |
RootedObject streamGlobal(cx); | |
RootedObject stream(cx); | |
CHECK(createExternalSourceStream(compartmentMode, &streamGlobal, &stream)); | |
// Getting the underlying source locks the stream. | |
- void* underlyingSource; | |
+ ReadableStreamUnderlyingSource* underlyingSource; | |
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource)); | |
- CHECK(underlyingSource == &stubExternalUnderlyingSource); | |
+ CHECK(underlyingSource == &StubExternalUnderlyingSource::instance); | |
bool locked; | |
CHECK(ReadableStreamIsLocked(cx, stream, &locked)); | |
CHECK(locked); | |
CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); | |
// Make some data available. | |
size_t length = sizeof(testBufferData); | |
CHECK(ReadableStreamUpdateDataAvailableFromSource(cx, stream, length)); | |
// Read from the stream. | |
RootedValue streamVal(cx, ObjectValue(*stream)); | |
CHECK(JS_SetProperty(cx, global, "stream", streamVal)); | |
RootedValue rval(cx); | |
EVAL(evalSrc, &rval); | |
- CHECK(writeIntoRequestBufferCBCalled); | |
+ CHECK(StubExternalUnderlyingSource::instance.writeIntoRequestBufferCBCalled); | |
CHECK(rval.isObject()); | |
RootedObject unwrappedPromise(cx, js::CheckedUnwrap(&rval.toObject())); | |
CHECK(unwrappedPromise); | |
CHECK(IsPromiseObject(unwrappedPromise)); | |
CHECK(GetPromiseState(unwrappedPromise) == PromiseState::Fulfilled); | |
RootedObject chunk(cx); | |
{ | |
JSAutoRealm ar(cx, unwrappedPromise); | |
@@ -764,33 +728,33 @@ BEGIN_FIXTURE_TEST(StreamTestFixture, | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamOtherGlobalDefaultReaderRead) | |
BEGIN_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamGetExternalUnderlyingSource) | |
{ | |
- ResetCallbacks(); | |
+ StubExternalUnderlyingSource::instance.reset(); | |
RootedObject stream(cx, NewExternalSourceStream(cx)); | |
CHECK(stream); | |
- void* source; | |
+ ReadableStreamUnderlyingSource* source; | |
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &source)); | |
- CHECK(source == &stubExternalUnderlyingSource); | |
+ CHECK(source == &StubExternalUnderlyingSource::instance); | |
CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); | |
RootedObject otherGlobal(cx, createGlobal()); | |
CHECK(otherGlobal); | |
{ | |
JSAutoRealm ar(cx, otherGlobal); | |
CHECK(JS_WrapObject(cx, &stream)); | |
- void* source; | |
+ ReadableStreamUnderlyingSource* source; | |
CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &source)); | |
- CHECK(source == &stubExternalUnderlyingSource); | |
+ CHECK(source == &StubExternalUnderlyingSource::instance); | |
CHECK(ReadableStreamReleaseExternalUnderlyingSource(cx, stream)); | |
} | |
return true; | |
} | |
END_FIXTURE_TEST(StreamTestFixture, | |
testReadableStream_ReadableStreamGetExternalUnderlyingSource) | |
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp | |
--- a/js/src/vm/Runtime.cpp | |
+++ b/js/src/vm/Runtime.cpp | |
@@ -95,22 +95,16 @@ JSRuntime::JSRuntime(JSRuntime* parentRu | |
#ifdef DEBUG | |
updateChildRuntimeCount(parentRuntime), | |
initialized_(false), | |
#endif | |
mainContext_(nullptr), | |
profilerSampleBufferRangeStart_(0), | |
telemetryCallback(nullptr), | |
consumeStreamCallback(nullptr), | |
- readableStreamDataRequestCallback(nullptr), | |
- readableStreamWriteIntoReadRequestCallback(nullptr), | |
- readableStreamCancelCallback(nullptr), | |
- readableStreamClosedCallback(nullptr), | |
- readableStreamErroredCallback(nullptr), | |
- readableStreamFinalizeCallback(nullptr), | |
hadOutOfMemory(false), | |
allowRelazificationForTesting(false), | |
destroyCompartmentCallback(nullptr), | |
sizeOfIncludingThisCompartmentCallback(nullptr), | |
destroyRealmCallback(nullptr), | |
realmNameCallback(nullptr), | |
externalStringSizeofCallback(nullptr), | |
securityCallbacks(&NullSecurityCallbacks), | |
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h | |
--- a/js/src/vm/Runtime.h | |
+++ b/js/src/vm/Runtime.h | |
@@ -341,23 +341,16 @@ struct JSRuntime : public js::MallocProv | |
js::UnprotectedData<JS::ConsumeStreamCallback> consumeStreamCallback; | |
js::GlobalObject* getIncumbentGlobal(JSContext* cx); | |
bool enqueuePromiseJob(JSContext* cx, js::HandleFunction job, js::HandleObject promise, | |
js::Handle<js::GlobalObject*> incumbentGlobal); | |
void addUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise); | |
void removeUnhandledRejectedPromise(JSContext* cx, js::HandleObject promise); | |
- js::UnprotectedData<JS::RequestReadableStreamDataCallback> readableStreamDataRequestCallback; | |
- js::UnprotectedData<JS::WriteIntoReadRequestBufferCallback> readableStreamWriteIntoReadRequestCallback; | |
- js::UnprotectedData<JS::CancelReadableStreamCallback> readableStreamCancelCallback; | |
- js::UnprotectedData<JS::ReadableStreamClosedCallback> readableStreamClosedCallback; | |
- js::UnprotectedData<JS::ReadableStreamErroredCallback> readableStreamErroredCallback; | |
- js::UnprotectedData<JS::ReadableStreamFinalizeCallback> readableStreamFinalizeCallback; | |
- | |
/* Had an out-of-memory error which did not populate an exception. */ | |
mozilla::Atomic<bool, mozilla::SequentiallyConsistent, | |
mozilla::recordreplay::Behavior::DontPreserve> hadOutOfMemory; | |
/* | |
* Allow relazifying functions in compartments that are active. This is | |
* only used by the relazifyFunctions() testing function. | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment