summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMoonchild <moonchild@palemoon.org>2023-10-03 17:23:19 +0000
committerMoonchild <moonchild@palemoon.org>2023-10-03 17:23:19 +0000
commit5d76f359905f10fbd5b4c53ac458e26d22ffe2a1 (patch)
tree912d084cee32880dad7b06fcd1fdc1fc71ce5b95
parent682e5a4727b8bdf33a670c5d08e9cb382f019a57 (diff)
parent054f135a5455967009587e12c14908904ab4105a (diff)
downloaduxp-5d76f359905f10fbd5b4c53ac458e26d22ffe2a1.tar.gz
Merge pull request 'Initial implementation of readable streams' (#2324) from dbsoft/UXP:readablestreams into master
Reviewed-on: https://repo.palemoon.org/MoonchildProductions/UXP/pulls/2324
-rw-r--r--dom/base/FormData.cpp4
-rw-r--r--dom/base/Navigator.cpp131
-rw-r--r--dom/base/Navigator.h19
-rw-r--r--dom/base/nsContentUtils.cpp90
-rw-r--r--dom/base/nsContentUtils.h6
-rw-r--r--dom/bindings/Bindings.conf7
-rw-r--r--dom/bindings/Codegen.py164
-rw-r--r--dom/bindings/ReadableStream.h28
-rw-r--r--dom/bindings/SpiderMonkeyInterface.h144
-rw-r--r--dom/bindings/TypedArray.h134
-rw-r--r--dom/bindings/moz.build2
-rw-r--r--dom/bindings/parser/WebIDL.py39
-rw-r--r--dom/cache/AutoUtils.cpp8
-rw-r--r--dom/cache/AutoUtils.h2
-rw-r--r--dom/cache/Cache.cpp40
-rw-r--r--dom/cache/Cache.h16
-rw-r--r--dom/cache/CacheOpChild.cpp12
-rw-r--r--dom/cache/CacheStorage.cpp9
-rw-r--r--dom/cache/CacheStorage.h6
-rw-r--r--dom/cache/CacheWorkerHolder.cpp31
-rw-r--r--dom/cache/CacheWorkerHolder.h8
-rw-r--r--dom/cache/TypeUtils.cpp25
-rw-r--r--dom/cache/TypeUtils.h12
-rw-r--r--dom/canvas/CanvasRenderingContext2D.cpp8
-rw-r--r--dom/canvas/ImageBitmap.cpp2
-rw-r--r--dom/canvas/WebGLTextureUpload.cpp4
-rw-r--r--dom/crypto/WebCryptoTask.cpp4
-rw-r--r--dom/fetch/BodyExtractor.cpp207
-rw-r--r--dom/fetch/BodyExtractor.h48
-rw-r--r--dom/fetch/Fetch.cpp514
-rw-r--r--dom/fetch/Fetch.h519
-rw-r--r--dom/fetch/FetchStream.cpp640
-rw-r--r--dom/fetch/FetchStream.h158
-rw-r--r--dom/fetch/FetchStreamReader.cpp405
-rw-r--r--dom/fetch/FetchStreamReader.h85
-rw-r--r--dom/fetch/InternalResponse.cpp6
-rw-r--r--dom/fetch/InternalResponse.h8
-rw-r--r--dom/fetch/Request.cpp42
-rw-r--r--dom/fetch/Request.h3
-rw-r--r--dom/fetch/Response.cpp190
-rw-r--r--dom/fetch/Response.h9
-rw-r--r--dom/fetch/moz.build5
-rw-r--r--dom/network/TCPSocketParent.cpp2
-rw-r--r--dom/url/URLSearchParams.cpp5
-rw-r--r--dom/webidl/Fetch.webidl14
-rw-r--r--dom/webidl/Navigator.webidl2
-rw-r--r--dom/webidl/Response.webidl10
-rw-r--r--dom/workers/RuntimeService.cpp1
-rw-r--r--dom/workers/ScriptLoader.cpp14
-rw-r--r--dom/workers/ServiceWorkerEvents.cpp130
-rw-r--r--dom/workers/ServiceWorkerScriptCache.cpp10
-rw-r--r--dom/workers/WorkerHolder.cpp9
-rw-r--r--dom/workers/WorkerHolder.h11
-rw-r--r--dom/workers/WorkerPrefs.h1
-rw-r--r--dom/workers/WorkerPrivate.cpp15
-rw-r--r--dom/workers/WorkerPrivate.h1
-rw-r--r--dom/xhr/XMLHttpRequestMainThread.cpp192
-rw-r--r--dom/xhr/XMLHttpRequestMainThread.h52
-rw-r--r--dom/xhr/nsIXMLHttpRequest.idl5
-rw-r--r--js/public/Stream.h510
-rw-r--r--js/src/builtin/Stream.cpp5488
-rw-r--r--js/src/builtin/Stream.h170
-rw-r--r--js/src/builtin/TestingFunctions.cpp12
-rw-r--r--js/src/builtin/TypedArray.js6
-rw-r--r--js/src/builtin/Utilities.js1
-rw-r--r--js/src/js.msg30
-rw-r--r--js/src/jsapi-tests/moz.build5
-rw-r--r--js/src/jsapi-tests/testIntTypesABI.cpp1
-rw-r--r--js/src/jsapi-tests/testReadableStream.cpp676
-rw-r--r--js/src/jsapi-tests/tests.cpp4
-rw-r--r--js/src/jsapi.cpp366
-rw-r--r--js/src/jsapi.h12
-rw-r--r--js/src/jsfriendapi.h9
-rw-r--r--js/src/jsnum.cpp11
-rw-r--r--js/src/jsnum.h4
-rw-r--r--js/src/jsprototypes.h11
-rw-r--r--js/src/moz.build2
-rw-r--r--js/src/shell/js.cpp9
-rw-r--r--js/src/vm/ArrayBufferObject.cpp20
-rw-r--r--js/src/vm/ArrayBufferObject.h6
-rw-r--r--js/src/vm/CommonPropertyNames.h53
-rw-r--r--js/src/vm/GlobalObject.cpp11
-rw-r--r--js/src/vm/Runtime.cpp6
-rw-r--r--js/src/vm/Runtime.h7
-rw-r--r--js/src/vm/SelfHosting.cpp22
-rw-r--r--js/src/vm/SharedArrayObject.cpp9
-rw-r--r--js/src/vm/SharedArrayObject.h6
-rw-r--r--js/xpconnect/src/XPCJSContext.cpp5
-rw-r--r--modules/libpref/init/all.js6
89 files changed, 10648 insertions, 1098 deletions
diff --git a/dom/base/FormData.cpp b/dom/base/FormData.cpp
index de4d71a676..d47a970fc8 100644
--- a/dom/base/FormData.cpp
+++ b/dom/base/FormData.cpp
@@ -398,7 +398,7 @@ FormData::Constructor(const GlobalObject& aGlobal,
NS_IMETHODIMP
FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset)
+ nsACString& aContentTypeWithCharset, nsACString& aCharset)
{
FSMultipartFormData fs(NS_LITERAL_CSTRING("UTF-8"), nullptr);
@@ -419,7 +419,7 @@ FormData::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
}
}
- fs.GetContentType(aContentType);
+ fs.GetContentType(aContentTypeWithCharset);
aCharset.Truncate();
*aContentLength = 0;
NS_ADDREF(*aBody = fs.GetSubmissionBody(aContentLength));
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index 05268a5156..0bf9ccbf45 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -11,7 +11,9 @@
#include "nsPluginArray.h"
#include "nsMimeTypeArray.h"
#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/BodyExtractor.h"
#include "mozilla/dom/DesktopNotification.h"
+#include "mozilla/dom/FetchBinding.h"
#include "mozilla/dom/File.h"
#include "nsGeolocation.h"
#include "nsIClassOfService.h"
@@ -38,6 +40,7 @@
#include "mozilla/dom/ServiceWorkerContainer.h"
#include "mozilla/dom/StorageManager.h"
#include "mozilla/dom/TCPSocket.h"
+#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/workers/RuntimeService.h"
#include "mozilla/Hal.h"
#include "nsISiteSpecificUserAgent.h"
@@ -845,9 +848,53 @@ BeaconStreamListener::OnDataAvailable(nsIRequest *aRequest,
bool
Navigator::SendBeacon(const nsAString& aUrl,
- const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
+ const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv)
{
+ if (aData.IsNull()) {
+ return SendBeaconInternal(aUrl, nullptr, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aData.Value().GetAsArrayBuffer());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(&aData.Value().GetAsArrayBufferView());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsBlob()) {
+ BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsBlob());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsFormData()) {
+ BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsFormData());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aData.Value().GetAsUSVString());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ if (aData.Value().IsURLSearchParams()) {
+ BodyExtractor<nsIXHRSendable> body(&aData.Value().GetAsURLSearchParams());
+ return SendBeaconInternal(aUrl, &body, eBeaconTypeOther, aRv);
+ }
+
+ MOZ_CRASH("Invalid data type.");
+ return false;
+}
+
+bool
+Navigator::SendBeaconInternal(const nsAString& aUrl,
+ BodyExtractorBase* aBody,
+ BeaconType aType,
+ ErrorResult& aRv)
+{
if (!mWindow) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return false;
@@ -887,9 +934,9 @@ Navigator::SendBeacon(const nsAString& aUrl,
nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
// No need to use CORS for sendBeacon unless it's a BLOB
- nsSecurityFlags securityFlags = (!aData.IsNull() && aData.Value().IsBlob())
- ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
- : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
+ nsSecurityFlags securityFlags = aType == eBeaconTypeBlob
+ ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
+ : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
nsCOMPtr<nsIChannel> channel;
@@ -915,76 +962,30 @@ Navigator::SendBeacon(const nsAString& aUrl,
}
httpChannel->SetReferrer(documentURI);
- nsCString mimeType;
- if (!aData.IsNull()) {
- nsCOMPtr<nsIInputStream> in;
-
- if (aData.Value().IsString()) {
- nsCString stringData = NS_ConvertUTF16toUTF8(aData.Value().GetAsString());
- nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
- if (NS_FAILED(rv)) {
- aRv.Throw(NS_ERROR_FAILURE);
- return false;
- }
- rv = strStream->SetData(stringData.BeginReading(), stringData.Length());
- if (NS_FAILED(rv)) {
- aRv.Throw(NS_ERROR_FAILURE);
- return false;
- }
- mimeType.AssignLiteral("text/plain;charset=UTF-8");
- in = strStream;
-
- } else if (aData.Value().IsArrayBufferView()) {
-
- nsCOMPtr<nsIStringInputStream> strStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
- if (NS_FAILED(rv)) {
- aRv.Throw(NS_ERROR_FAILURE);
- return false;
- }
-
- const ArrayBufferView& view = aData.Value().GetAsArrayBufferView();
- view.ComputeLengthAndData();
- rv = strStream->SetData(reinterpret_cast<char*>(view.Data()),
- view.Length());
-
- if (NS_FAILED(rv)) {
- aRv.Throw(NS_ERROR_FAILURE);
- return false;
- }
- mimeType.AssignLiteral("application/octet-stream");
- in = strStream;
-
- } else if (aData.Value().IsBlob()) {
- Blob& blob = aData.Value().GetAsBlob();
- blob.GetInternalStream(getter_AddRefs(in), aRv);
- if (NS_WARN_IF(aRv.Failed())) {
- return false;
- }
+ nsCOMPtr<nsIInputStream> in;
+ nsAutoCString contentTypeWithCharset;
+ nsAutoCString charset;
+ uint64_t length = 0;
- nsAutoString type;
- blob.GetType(type);
- mimeType = NS_ConvertUTF16toUTF8(type);
-
- } else if (aData.Value().IsFormData()) {
- FormData& form = aData.Value().GetAsFormData();
- uint64_t len;
- nsAutoCString charset;
- form.GetSendInfo(getter_AddRefs(in),
- &len,
- mimeType,
- charset);
- } else {
- MOZ_ASSERT(false, "switch statements not in sync");
- aRv.Throw(NS_ERROR_FAILURE);
+ if (aBody) {
+ aRv = aBody->GetAsStream(getter_AddRefs(in), &length,
+ contentTypeWithCharset, charset);
+ if (NS_WARN_IF(aRv.Failed())) {
return false;
}
+ if (aType == eBeaconTypeArrayBuffer) {
+ MOZ_ASSERT(contentTypeWithCharset.IsEmpty());
+ MOZ_ASSERT(charset.IsEmpty());
+ contentTypeWithCharset.Assign("application/octet-stream");
+ }
+
nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(channel);
if (!uploadChannel) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
- uploadChannel->ExplicitSetUploadStream(in, mimeType, -1,
+ uploadChannel->ExplicitSetUploadStream(in, contentTypeWithCharset, length,
NS_LITERAL_CSTRING("POST"),
false);
} else {
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index 2915b50692..175940e82d 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -7,6 +7,7 @@
#define mozilla_dom_Navigator_h
#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/ErrorResult.h"
#include "nsIDOMNavigator.h"
@@ -30,12 +31,13 @@ class nsIURI;
namespace mozilla {
namespace dom {
+class BodyExtractorBase;
class Geolocation;
class systemMessageCallback;
class MediaDevices;
struct MediaStreamConstraints;
class WakeLock;
-class ArrayBufferViewOrBlobOrStringOrFormData;
+class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
class ServiceWorkerContainer;
class DOMRequest;
} // namespace dom
@@ -196,7 +198,7 @@ public:
#endif // MOZ_AUDIO_CHANNEL_MANAGER
bool SendBeacon(const nsAString& aUrl,
- const Nullable<ArrayBufferViewOrBlobOrStringOrFormData>& aData,
+ const Nullable<fetch::BodyInit>& aData,
ErrorResult& aRv);
void MozGetUserMedia(const MediaStreamConstraints& aConstraints,
@@ -253,6 +255,19 @@ private:
bool CheckPermission(const char* type);
static bool CheckPermission(nsPIDOMWindowInner* aWindow, const char* aType);
+ // This enum helps SendBeaconInternal to apply different behaviors to body
+ // types.
+ enum BeaconType {
+ eBeaconTypeBlob,
+ eBeaconTypeArrayBuffer,
+ eBeaconTypeOther
+ };
+
+ bool SendBeaconInternal(const nsAString& aUrl,
+ BodyExtractorBase* aBody,
+ BeaconType aType,
+ ErrorResult& aRv);
+
RefPtr<nsMimeTypeArray> mMimeTypes;
RefPtr<nsPluginArray> mPlugins;
RefPtr<Permissions> mPermissions;
diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp
index 86e52984af..4c00f358f2 100644
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -39,6 +39,8 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DocumentFragment.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/DOMTypes.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FileSystemSecurity.h"
@@ -8735,6 +8737,25 @@ nsContentUtils::PushEnabled(JSContext* aCx, JSObject* aObj)
// static
bool
+nsContentUtils::StreamsEnabled(JSContext* aCx, JSObject* aObj)
+{
+ if (NS_IsMainThread()) {
+ return Preferences::GetBool("dom.streams.enabled", false);
+ }
+
+ using namespace workers;
+
+ // Otherwise, check the pref via the WorkerPrivate
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (!workerPrivate) {
+ return false;
+ }
+
+ return workerPrivate->StreamsEnabled();
+}
+
+// static
+bool
nsContentUtils::IsNonSubresourceRequest(nsIChannel* aChannel)
{
nsLoadFlags loadFlags = 0;
@@ -10060,3 +10081,72 @@ void nsContentUtils::StructuredClone(JSContext* aCx,
nsTArray<RefPtr<MessagePort>> ports = holder.TakeTransferredPorts();
Unused << ports;
}
+
+/* static */ void
+nsContentUtils::ExtractErrorValues(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ nsACString& aSourceSpecOut,
+ uint32_t* aLineOut,
+ uint32_t* aColumnOut,
+ nsString& aMessageOut)
+{
+ MOZ_ASSERT(aLineOut);
+ MOZ_ASSERT(aColumnOut);
+
+ if (aValue.isObject()) {
+ JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+ RefPtr<dom::DOMException> domException;
+
+ // Try to process as an Error object. Use the file/line/column values
+ // from the Error as they will be more specific to the root cause of
+ // the problem.
+ JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
+ if (err) {
+ // Use xpc to extract the error message only. We don't actually send
+ // this report anywhere.
+ RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
+ report->Init(err,
+ "<unknown>", // toString result
+ false, // chrome
+ 0); // window ID
+
+ if (!report->mFileName.IsEmpty()) {
+ CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
+ *aLineOut = report->mLineNumber;
+ *aColumnOut = report->mColumn;
+ }
+ aMessageOut.Assign(report->mErrorMsg);
+ }
+
+ // Next, try to unwrap the rejection value as a DOMException.
+ else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
+
+ nsAutoString filename;
+ domException->GetFilename(aCx, filename);
+ if (!filename.IsEmpty()) {
+ CopyUTF16toUTF8(filename, aSourceSpecOut);
+ *aLineOut = domException->LineNumber(aCx);
+ *aColumnOut = domException->ColumnNumber();
+ }
+
+ domException->GetName(aMessageOut);
+ aMessageOut.AppendLiteral(": ");
+
+ nsAutoString message;
+ domException->GetMessageMoz(message);
+ aMessageOut.Append(message);
+ }
+ }
+
+ // If we could not unwrap a specific error type, then perform default safe
+ // string conversions on primitives. Objects will result in "[Object]"
+ // unfortunately.
+ if (aMessageOut.IsEmpty()) {
+ nsAutoJSString jsString;
+ if (jsString.init(aCx, aValue)) {
+ aMessageOut = jsString;
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+}
diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h
index 58f9f56546..6e9f23054a 100644
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1010,6 +1010,10 @@ public:
static bool PrefetchEnabled(nsIDocShell* aDocShell);
+ static void ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ nsACString& aSourceSpecOut, uint32_t *aLineOut,
+ uint32_t *aColumnOut, nsString& aMessageOut);
+
static nsresult CalculateBufferSizeForImage(const uint32_t& aStride,
const mozilla::gfx::IntSize& aImageSize,
const mozilla::gfx::SurfaceFormat& aFormat,
@@ -2679,6 +2683,8 @@ public:
static mozilla::net::ReferrerPolicy GetReferrerPolicyFromHeader(const nsAString& aHeader);
static bool PushEnabled(JSContext* aCx, JSObject* aObj);
+
+ static bool StreamsEnabled(JSContext* aCx, JSObject* aObj);
static bool IsNonSubresourceRequest(nsIChannel* aChannel);
diff --git a/dom/bindings/Bindings.conf b/dom/bindings/Bindings.conf
index a12a294766..17bbed79d5 100644
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -103,11 +103,13 @@ DOMInterfaces = {
},
'Cache': {
- 'implicitJSContext': [ 'add', 'addAll' ],
+ 'implicitJSContext': [ 'add', 'addAll', 'match', 'matchAll', 'put',
+ 'delete', 'keys' ],
'nativeType': 'mozilla::dom::cache::Cache',
},
'CacheStorage': {
+ 'implicitJSContext': [ 'match' ],
'nativeType': 'mozilla::dom::cache::CacheStorage',
},
@@ -705,6 +707,7 @@ DOMInterfaces = {
'headers': 'headers_',
'referrerPolicy': 'referrerPolicy_'
},
+ 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text' ],
},
'ResizeObservation': {
@@ -729,6 +732,8 @@ DOMInterfaces = {
'Response': {
'binaryNames': { 'headers': 'headers_' },
+ 'implicitJSContext': [ 'arrayBuffer', 'blob', 'formData', 'json', 'text',
+ 'clone', 'cloneUnfiltered' ],
},
'RGBColor': {
diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py
index a70253fc3a..b7caaad7bb 100644
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1156,7 +1156,10 @@ class CGHeaders(CGWrapper):
# just include their header if we need to have functions
# taking references to them declared in that header.
headerSet = declareIncludes
- headerSet.add("mozilla/dom/TypedArray.h")
+ if unrolled.isReadableStream():
+ headerSet.add("mozilla/dom/ReadableStream.h")
+ else:
+ headerSet.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(unrolled.inner.identifier.name)
@@ -1371,7 +1374,10 @@ def UnionTypes(unionTypes, config):
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("jsfriendapi.h")
- headers.add("mozilla/dom/TypedArray.h")
+ if f.isReadableStream():
+ headers.add("mozilla/dom/ReadableStream.h")
+ else:
+ headers.add("mozilla/dom/TypedArray.h")
else:
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
@@ -1457,7 +1463,10 @@ def UnionConversions(unionTypes, config):
elif f.isInterface():
if f.isSpiderMonkeyInterface():
headers.add("jsfriendapi.h")
- headers.add("mozilla/dom/TypedArray.h")
+ if f.isReadableStream():
+ headers.add("mozilla/dom/ReadableStream.h")
+ else:
+ headers.add("mozilla/dom/TypedArray.h")
elif f.inner.isExternal():
try:
typeDesc = config.getDescriptor(f.inner.identifier.name)
@@ -5582,8 +5591,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if type.isSpiderMonkeyInterface():
assert not isEnforceRange and not isClamp
name = type.unroll().name # unroll() because it may be nullable
- arrayType = CGGeneric(name)
- declType = arrayType
+ interfaceType = CGGeneric(name)
+ declType = interfaceType
if type.nullable():
declType = CGTemplatedType("Nullable", declType)
objRef = "${declName}.SetValue()"
@@ -5607,22 +5616,23 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
# This is a bit annoying. In a union we don't want to have a
# holder, since unions don't support that. But if we're optional we
# want to have a holder, so that the callee doesn't see
- # Optional<RootedTypedArray<ArrayType> >. So do a holder if we're
- # optional and use a RootedTypedArray otherwise.
+ # Optional<RootedSpiderMonkeyInterface<InterfaceType>>. So do a
+ # holder if we're optional and use a RootedSpiderMonkeyInterface
+ # otherwise.
if isOptional:
- holderType = CGTemplatedType("TypedArrayRooter", arrayType)
- # If our typed array is nullable, this will set the Nullable to
- # be not-null, but that's ok because we make an explicit
- # SetNull() call on it as needed if our JS value is actually
- # null. XXXbz Because "Maybe" takes const refs for constructor
- # arguments, we can't pass a reference here; have to pass a
- # pointer.
+ holderType = CGTemplatedType("SpiderMonkeyInterfaceRooter", interfaceType)
+ # If our SpiderMonkey interface is nullable, this will set the
+ # Nullable to be not-null, but that's ok because we make an
+ # explicit SetNull() call on it as needed if our JS value is
+ # actually null. XXXbz Because "Maybe" takes const refs for
+ # constructor arguments, we can't pass a reference here; have
+ # to pass a pointer.
holderArgs = "cx, &%s" % objRef
declArgs = None
else:
holderType = None
holderArgs = None
- declType = CGTemplatedType("RootedTypedArray", declType)
+ declType = CGTemplatedType("RootedSpiderMonkeyInterface", declType)
declArgs = "cx"
else:
holderType = None
@@ -6371,7 +6381,7 @@ def getMaybeWrapValueFuncForType(type):
if type.nullable():
return "MaybeWrapObjectOrNullValue"
return "MaybeWrapObjectValue"
- # Spidermonkey interfaces are never DOM objects. Neither are sequences or
+ # SpiderMonkey interfaces are never DOM objects. Neither are sequences or
# dictionaries, since those are always plain JS objects.
if type.isSpiderMonkeyInterface() or type.isDictionary() or type.isSequence():
if type.nullable():
@@ -6391,7 +6401,8 @@ recordWrapLevel = 0
def getWrapTemplateForType(type, descriptorProvider, result, successCode,
- returnsNewObject, exceptionCode, typedArraysAreStructs,
+ returnsNewObject, exceptionCode,
+ spiderMonkeyInterfacesAreStructs,
isConstructorRetval=False):
"""
Reflect a C++ value stored in "result", of IDL type "type" into JS. The
@@ -6401,8 +6412,9 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
doing a 'break' if the entire conversion template is inside a block that
the 'break' will exit).
- If typedArraysAreStructs is true, then if the type is a typed array,
- "result" is one of the dom::TypedArray subclasses, not a JSObject*.
+ If spiderMonkeyInterfacesAreStructs is true, then if the type is a
+ SpiderMonkey interface, "result" is one of the
+ dom::SpiderMonkeyInterfaceObjectStorage subclasses, not a JSObject*.
The resulting string should be used with string.Template. It
needs the following keys when substituting:
@@ -6497,7 +6509,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
recTemplate, recInfall = getWrapTemplateForType(type.inner, descriptorProvider,
"%s.Value()" % result, successCode,
returnsNewObject, exceptionCode,
- typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs)
code = fill(
"""
@@ -6529,7 +6541,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
'returnsNewObject': returnsNewObject,
'exceptionCode': exceptionCode,
'obj': "returnArray",
- 'typedArraysAreStructs': typedArraysAreStructs
+ 'spiderMonkeyInterfacesAreStructs': spiderMonkeyInterfacesAreStructs
})
sequenceWrapLevel -= 1
code = fill(
@@ -6583,7 +6595,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
'returnsNewObject': returnsNewObject,
'exceptionCode': exceptionCode,
'obj': "returnObj",
- 'typedArraysAreStructs': typedArraysAreStructs
+ 'spiderMonkeyInterfacesAreStructs': spiderMonkeyInterfacesAreStructs
})
recordWrapLevel -= 1
if type.keyType.isByteString():
@@ -6749,7 +6761,7 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
return (head + _setValue(result, wrapAsType=type), False)
if (type.isObject() or (type.isSpiderMonkeyInterface() and
- not typedArraysAreStructs)):
+ not spiderMonkeyInterfacesAreStructs)):
# See comments in GetOrCreateDOMReflector explaining why we need
# to wrap here.
if type.nullable():
@@ -6768,21 +6780,21 @@ def getWrapTemplateForType(type, descriptorProvider, result, successCode,
if not (type.isUnion() or type.isPrimitive() or type.isDictionary() or
type.isDate() or
- (type.isSpiderMonkeyInterface() and typedArraysAreStructs)):
+ (type.isSpiderMonkeyInterface() and spiderMonkeyInterfacesAreStructs)):
raise TypeError("Need to learn to wrap %s" % type)
if type.nullable():
recTemplate, recInfal = getWrapTemplateForType(type.inner, descriptorProvider,
"%s.Value()" % result, successCode,
returnsNewObject, exceptionCode,
- typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs)
return ("if (%s.IsNull()) {\n" % result +
indent(setNull()) +
"}\n" +
recTemplate, recInfal)
if type.isSpiderMonkeyInterface():
- assert typedArraysAreStructs
+ assert spiderMonkeyInterfacesAreStructs
# See comments in GetOrCreateDOMReflector explaining why we need
# to wrap here.
# NB: setObject(..., some-object-type) calls JS_WrapValue(), so is fallible
@@ -6864,7 +6876,7 @@ def wrapForType(type, descriptorProvider, templateValues):
templateValues.get('successCode', None),
templateValues.get('returnsNewObject', False),
templateValues.get('exceptionCode', "return false;\n"),
- templateValues.get('typedArraysAreStructs', False),
+ templateValues.get('spiderMonkeyInterfacesAreStructs', False),
isConstructorRetval=templateValues.get('isConstructorRetval', False))[0]
defaultValues = {'obj': 'obj'}
@@ -8193,10 +8205,10 @@ class CGMethodCall(CGThing):
# "object" arg.
# First grab all the overloads that have a non-callback interface
- # (which includes typed arrays and arraybuffers) at the
- # distinguishing index. We can also include the ones that have an
- # "object" here, since if those are present no other object-typed
- # argument will be.
+ # (which includes SpiderMonkey interfaces) at the distinguishing
+ # index. We can also include the ones that have an "object" here,
+ # since if those are present no other object-typed argument will
+ # be.
objectSigs = [
s for s in possibleSignatures
if (distinguishingType(s).isObject() or
@@ -8227,16 +8239,17 @@ class CGMethodCall(CGThing):
# There might be more than one thing in objectSigs; we need to check
# which ones we unwrap to.
if len(objectSigs) > 0:
- # Here it's enough to guard on our argument being an object. The
- # code for unwrapping non-callback interfaces, typed arrays,
- # sequences, and Dates will just bail out and move on to
- # the next overload if the object fails to unwrap correctly,
- # while "object" accepts any object anyway. We could even not
- # do the isObject() check up front here, but in cases where we
- # have multiple object overloads it makes sense to do it only
- # once instead of for each overload. That will also allow the
- # unwrapping test to skip having to do codegen for the
- # null-or-undefined case, which we already handled above.
+ # Here it's enough to guard on our argument being an object.
+ # The code for unwrapping non-callback interfaces, spiderMonkey
+ # interfaces, sequences, and Dates will just bail out and move
+ # on to the next overload if the object fails to unwrap
+ # correctly, while "object" accepts any object anyway. We
+ # could even not do the isObject() check up front here, but in
+ # cases where we have multiple object overloads it makes sense
+ # to do it only once instead of for each overload. That will
+ # also allow the unwrapping test to skip having to do codegen
+ # for the null-or-undefined case, which we already handled
+ # above.
caseBody.append(CGGeneric("if (%s.isObject()) {\n" %
distinguishingArg))
for sig in objectSigs:
@@ -10239,7 +10252,7 @@ class CGUnionStruct(CGThing):
"jsvalHandle": "rval",
"obj": "scopeObj",
"result": val,
- "typedArraysAreStructs": True
+ "spiderMonkeyInterfacesAreStructs": True
})
return CGGeneric(wrapCode)
@@ -13219,7 +13232,7 @@ class CGDictionary(CGThing):
# 'obj' can just be allowed to be the string "obj", since that
# will be our dictionary object, which is presumably itself in
# the right scope.
- 'typedArraysAreStructs': True
+ 'spiderMonkeyInterfacesAreStructs': True
})
conversion = CGGeneric(innerTemplate)
conversion = CGWrapper(conversion,
@@ -13679,7 +13692,7 @@ class ForwardDeclarationBuilder:
except NoSuchDescriptorError:
pass
- # Note: Spidermonkey interfaces are typedefs, so can't be
+ # Note: SpiderMonkey interfaces are typedefs, so can't be
# forward-declared
elif t.isPromise():
self.addInMozillaDom("Promise")
@@ -13988,7 +14001,7 @@ class CGBindingRoot(CGThing):
return {desc.getDescriptor(desc.interface.parent.identifier.name)}
for x in dependencySortObjects(jsImplemented, getParentDescriptor,
lambda d: d.interface.identifier.name):
- cgthings.append(CGCallbackInterface(x, typedArraysAreStructs=True))
+ cgthings.append(CGCallbackInterface(x, spiderMonkeyInterfacesAreStructs=True))
cgthings.append(CGJSImplClass(x))
# And make sure we have the right number of newlines at the end
@@ -14050,14 +14063,13 @@ class CGBindingRoot(CGThing):
class CGNativeMember(ClassMethod):
def __init__(self, descriptorProvider, member, name, signature, extendedAttrs,
breakAfter=True, passJSBitsAsNeeded=True, visibility="public",
- typedArraysAreStructs=True, variadicIsSequence=False,
- resultNotAddRefed=False,
- virtual=False,
- override=False):
+ spiderMonkeyInterfacesAreStructs=True,
+ variadicIsSequence=False, resultNotAddRefed=False,
+ virtual=False, override=False):
"""
- If typedArraysAreStructs is false, typed arrays will be passed as
- JS::Handle<JSObject*>. If it's true they will be passed as one of the
- dom::TypedArray subclasses.
+ If spiderMonkeyInterfacesAreStructs is false, SpiderMonkey interfaces
+ will be passed as JS::Handle<JSObject*>. If it's true they will be
+ passed as one of the dom::SpiderMonkeyInterfaceObjectStorage subclasses.
If passJSBitsAsNeeded is false, we don't automatically pass in a
JSContext* or a JSObject* based on the return and argument types. We
@@ -14068,7 +14080,7 @@ class CGNativeMember(ClassMethod):
self.extendedAttrs = extendedAttrs
self.resultAlreadyAddRefed = not resultNotAddRefed
self.passJSBitsAsNeeded = passJSBitsAsNeeded
- self.typedArraysAreStructs = typedArraysAreStructs
+ self.spiderMonkeyInterfacesAreStructs = spiderMonkeyInterfacesAreStructs
self.variadicIsSequence = variadicIsSequence
breakAfterSelf = "\n" if breakAfter else ""
ClassMethod.__init__(self, name,
@@ -14375,7 +14387,7 @@ class CGNativeMember(ClassMethod):
False, False)
if type.isSpiderMonkeyInterface():
- if not self.typedArraysAreStructs:
+ if not self.spiderMonkeyInterfacesAreStructs:
return "JS::Handle<JSObject*>", False, False
# Unroll for the name, in case we're nullable.
@@ -15547,16 +15559,16 @@ class CGFastCallback(CGClass):
class CGCallbackInterface(CGCallback):
- def __init__(self, descriptor, typedArraysAreStructs=False):
+ def __init__(self, descriptor, spiderMonkeyInterfacesAreStructs=False):
iface = descriptor.interface
attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
- getters = [CallbackGetter(a, descriptor, typedArraysAreStructs)
+ getters = [CallbackGetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs]
- setters = [CallbackSetter(a, descriptor, typedArraysAreStructs)
+ setters = [CallbackSetter(a, descriptor, spiderMonkeyInterfacesAreStructs)
for a in attrs if not a.readonly]
methods = [m for m in iface.members
if m.isMethod() and not m.isStatic() and not m.isIdentifierLess()]
- methods = [CallbackOperation(m, sig, descriptor, typedArraysAreStructs)
+ methods = [CallbackOperation(m, sig, descriptor, spiderMonkeyInterfacesAreStructs)
for m in methods for sig in m.signatures()]
if iface.isJSImplemented() and iface.ctor():
sigs = descriptor.interface.ctor().signatures()
@@ -15601,7 +15613,8 @@ class CallbackMember(CGNativeMember):
# CallSetup already handled the unmark-gray bits for us. we don't have
# anything better to use for 'obj', really...
def __init__(self, sig, name, descriptorProvider, needThisHandling,
- rethrowContentException=False, typedArraysAreStructs=False,
+ rethrowContentException=False,
+ spiderMonkeyInterfacesAreStructs=False,
wrapScope='CallbackKnownNotGray()'):
"""
needThisHandling is True if we need to be able to accept a specified
@@ -15636,7 +15649,7 @@ class CallbackMember(CGNativeMember):
extendedAttrs={},
passJSBitsAsNeeded=False,
visibility=visibility,
- typedArraysAreStructs=typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
# We have to do all the generation of our body now, because
# the caller relies on us throwing if we can't manage it.
self.exceptionCode = ("aRv.Throw(NS_ERROR_UNEXPECTED);\n"
@@ -15753,7 +15766,7 @@ class CallbackMember(CGNativeMember):
'obj': self.wrapScope,
'returnsNewObject': False,
'exceptionCode': self.exceptionCode,
- 'typedArraysAreStructs': self.typedArraysAreStructs
+ 'spiderMonkeyInterfacesAreStructs': self.spiderMonkeyInterfacesAreStructs
})
except MethodNotNewObjectError as err:
raise TypeError("%s being passed as an argument to %s but is not "
@@ -15857,10 +15870,11 @@ class CallbackMember(CGNativeMember):
class CallbackMethod(CallbackMember):
def __init__(self, sig, name, descriptorProvider, needThisHandling,
- rethrowContentException=False, typedArraysAreStructs=False):
+ rethrowContentException=False,
+ spiderMonkeyInterfacesAreStructs=False):
CallbackMember.__init__(self, sig, name, descriptorProvider,
needThisHandling, rethrowContentException,
- typedArraysAreStructs=typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@@ -15919,12 +15933,12 @@ class CallbackOperationBase(CallbackMethod):
"""
def __init__(self, signature, jsName, nativeName, descriptor,
singleOperation, rethrowContentException=False,
- typedArraysAreStructs=False):
+ spiderMonkeyInterfacesAreStructs=False):
self.singleOperation = singleOperation
self.methodName = descriptor.binaryNameFor(jsName)
CallbackMethod.__init__(self, signature, nativeName, descriptor,
singleOperation, rethrowContentException,
- typedArraysAreStructs=typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getThisDecl(self):
if not self.singleOperation:
@@ -15975,7 +15989,8 @@ class CallbackOperation(CallbackOperationBase):
"""
Codegen actual WebIDL operations on callback interfaces.
"""
- def __init__(self, method, signature, descriptor, typedArraysAreStructs):
+ def __init__(self, method, signature, descriptor,
+ spiderMonkeyInterfacesAreStructs):
self.ensureASCIIName(method)
self.method = method
jsName = method.identifier.name
@@ -15984,7 +15999,7 @@ class CallbackOperation(CallbackOperationBase):
MakeNativeName(descriptor.binaryNameFor(jsName)),
descriptor, descriptor.interface.isSingleOperationInterface(),
rethrowContentException=descriptor.interface.isJSImplemented(),
- typedArraysAreStructs=typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getPrettyName(self):
return "%s.%s" % (self.descriptorProvider.interface.identifier.name,
@@ -15995,13 +16010,14 @@ class CallbackAccessor(CallbackMember):
"""
Shared superclass for CallbackGetter and CallbackSetter.
"""
- def __init__(self, attr, sig, name, descriptor, typedArraysAreStructs):
+ def __init__(self, attr, sig, name, descriptor,
+ spiderMonkeyInterfacesAreStructs):
self.ensureASCIIName(attr)
self.attrName = attr.identifier.name
CallbackMember.__init__(self, sig, name, descriptor,
needThisHandling=False,
rethrowContentException=descriptor.interface.isJSImplemented(),
- typedArraysAreStructs=typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs=spiderMonkeyInterfacesAreStructs)
def getPrettyName(self):
return "%s.%s" % (self.descriptorProvider.interface.identifier.name,
@@ -16009,12 +16025,12 @@ class CallbackAccessor(CallbackMember):
class CallbackGetter(CallbackAccessor):
- def __init__(self, attr, descriptor, typedArraysAreStructs):
+ def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
CallbackAccessor.__init__(self, attr,
(attr.type, []),
callbackGetterName(attr, descriptor),
descriptor,
- typedArraysAreStructs)
+ spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
@@ -16036,12 +16052,12 @@ class CallbackGetter(CallbackAccessor):
class CallbackSetter(CallbackAccessor):
- def __init__(self, attr, descriptor, typedArraysAreStructs):
+ def __init__(self, attr, descriptor, spiderMonkeyInterfacesAreStructs):
CallbackAccessor.__init__(self, attr,
(BuiltinTypes[IDLBuiltinType.Types.void],
[FakeArgument(attr.type, attr)]),
callbackSetterName(attr, descriptor),
- descriptor, typedArraysAreStructs)
+ descriptor, spiderMonkeyInterfacesAreStructs)
def getRvalDecl(self):
# We don't need an rval
@@ -16076,7 +16092,7 @@ class CGJSImplInitOperation(CallbackOperationBase):
"__init", "__Init", descriptor,
singleOperation=False,
rethrowContentException=True,
- typedArraysAreStructs=True)
+ spiderMonkeyInterfacesAreStructs=True)
def getPrettyName(self):
return "__init"
diff --git a/dom/bindings/ReadableStream.h b/dom/bindings/ReadableStream.h
new file mode 100644
index 0000000000..1ea7ac4d88
--- /dev/null
+++ b/dom/bindings/ReadableStream.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_ReadableStream_h
+#define mozilla_dom_ReadableStream_h
+
+#include "mozilla/dom/SpiderMonkeyInterface.h"
+
+namespace mozilla {
+namespace dom {
+
+struct ReadableStream : public SpiderMonkeyInterfaceObjectStorage
+{
+ inline bool Init(JSObject* obj)
+ {
+ MOZ_ASSERT(!inited());
+ mImplObj = mWrappedObj = js::UnwrapReadableStream(obj);
+ return inited();
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_ReadableStream_h */
diff --git a/dom/bindings/SpiderMonkeyInterface.h b/dom/bindings/SpiderMonkeyInterface.h
new file mode 100644
index 0000000000..f852afddaf
--- /dev/null
+++ b/dom/bindings/SpiderMonkeyInterface.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_SpiderMonkeyInterface_h
+#define mozilla_dom_SpiderMonkeyInterface_h
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/TracingAPI.h"
+
+namespace mozilla {
+namespace dom {
+
+/*
+ * Class that just handles the JSObject storage and tracing for spidermonkey
+ * interfaces
+ */
+struct SpiderMonkeyInterfaceObjectStorage
+{
+protected:
+ JSObject* mImplObj;
+ JSObject* mWrappedObj;
+
+ SpiderMonkeyInterfaceObjectStorage()
+ : mImplObj(nullptr),
+ mWrappedObj(nullptr)
+ {
+ }
+
+ SpiderMonkeyInterfaceObjectStorage(SpiderMonkeyInterfaceObjectStorage&& aOther)
+ : mImplObj(aOther.mImplObj),
+ mWrappedObj(aOther.mWrappedObj)
+ {
+ aOther.mImplObj = nullptr;
+ aOther.mWrappedObj = nullptr;
+ }
+
+public:
+ inline void TraceSelf(JSTracer* trc)
+ {
+ JS::UnsafeTraceRoot(trc, &mImplObj, "SpiderMonkeyInterfaceObjectStorage.mImplObj");
+ JS::UnsafeTraceRoot(trc, &mWrappedObj, "SpiderMonkeyInterfaceObjectStorage.mWrappedObj");
+ }
+
+ inline bool inited() const
+ {
+ return !!mImplObj;
+ }
+
+ inline bool WrapIntoNewCompartment(JSContext* cx)
+ {
+ return JS_WrapObject(cx,
+ JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
+ }
+
+ inline JSObject *Obj() const
+ {
+ MOZ_ASSERT(inited());
+ return mWrappedObj;
+ }
+
+private:
+ SpiderMonkeyInterfaceObjectStorage(const SpiderMonkeyInterfaceObjectStorage&) = delete;
+};
+
+// A class for rooting an existing SpiderMonkey Interface struct
+template<typename InterfaceType>
+class MOZ_RAII SpiderMonkeyInterfaceRooter : private JS::CustomAutoRooter
+{
+public:
+ template <typename CX>
+ SpiderMonkeyInterfaceRooter(const CX& cx,
+ InterfaceType* aInterface MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
+ mInterface(aInterface)
+ {
+ }
+
+ virtual void trace(JSTracer* trc) override
+ {
+ mInterface->TraceSelf(trc);
+ }
+
+private:
+ SpiderMonkeyInterfaceObjectStorage* const mInterface;
+};
+
+// And a specialization for dealing with nullable SpiderMonkey interfaces
+template<typename Inner> struct Nullable;
+template<typename InterfaceType>
+class MOZ_RAII SpiderMonkeyInterfaceRooter<Nullable<InterfaceType>> :
+ private JS::CustomAutoRooter
+{
+public:
+ template <typename CX>
+ SpiderMonkeyInterfaceRooter(const CX& cx,
+ Nullable<InterfaceType>* aInterface MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
+ mInterface(aInterface)
+ {
+ }
+
+ virtual void trace(JSTracer* trc) override
+ {
+ if (!mInterface->IsNull()) {
+ mInterface->Value().TraceSelf(trc);
+ }
+ }
+
+private:
+ Nullable<InterfaceType>* const mInterface;
+};
+
+// Class for easily setting up a rooted SpiderMonkey interface object on the
+// stack
+template<typename InterfaceType>
+class MOZ_RAII RootedSpiderMonkeyInterface final : public InterfaceType,
+ private SpiderMonkeyInterfaceRooter<InterfaceType>
+{
+public:
+ template <typename CX>
+ explicit RootedSpiderMonkeyInterface(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : InterfaceType(),
+ SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
+ {
+ }
+
+ template <typename CX>
+ RootedSpiderMonkeyInterface(const CX& cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+ : InterfaceType(obj),
+ SpiderMonkeyInterfaceRooter<InterfaceType>(cx, this
+ MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
+ {
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_SpiderMonkeyInterface_h */
diff --git a/dom/bindings/TypedArray.h b/dom/bindings/TypedArray.h
index 631707579b..8de0621d46 100644
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -6,52 +6,16 @@
#ifndef mozilla_dom_TypedArray_h
#define mozilla_dom_TypedArray_h
-#include "jsapi.h"
-#include "jsfriendapi.h"
-#include "js/RootingAPI.h"
-#include "js/TracingAPI.h"
#include "mozilla/Attributes.h"
#include "mozilla/Move.h"
#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/SpiderMonkeyInterface.h"
#include "nsWrapperCache.h"
namespace mozilla {
namespace dom {
/*
- * Class that just handles the JSObject storage and tracing for typed arrays
- */
-struct TypedArrayObjectStorage : AllTypedArraysBase {
-protected:
- JSObject* mTypedObj;
- JSObject* mWrappedObj;
-
- TypedArrayObjectStorage()
- : mTypedObj(nullptr),
- mWrappedObj(nullptr)
- {
- }
-
- TypedArrayObjectStorage(TypedArrayObjectStorage&& aOther)
- : mTypedObj(aOther.mTypedObj),
- mWrappedObj(aOther.mWrappedObj)
- {
- aOther.mTypedObj = nullptr;
- aOther.mWrappedObj = nullptr;
- }
-
-public:
- inline void TraceSelf(JSTracer* trc)
- {
- JS::UnsafeTraceRoot(trc, &mTypedObj, "TypedArray.mTypedObj");
- JS::UnsafeTraceRoot(trc, &mWrappedObj, "TypedArray.mWrappedObj");
- }
-
-private:
- TypedArrayObjectStorage(const TypedArrayObjectStorage&) = delete;
-};
-
-/*
* Various typed array classes for argument conversion. We have a base class
* that has a way of initializing a TypedArray from an existing typed array, and
* a subclass of the base class that supports creation of a relevant typed array
@@ -60,7 +24,9 @@ private:
template<typename T,
JSObject* UnwrapArray(JSObject*),
void GetLengthAndDataAndSharedness(JSObject*, uint32_t*, bool*, T**)>
-struct TypedArray_base : public TypedArrayObjectStorage {
+struct TypedArray_base : public SpiderMonkeyInterfaceObjectStorage,
+ AllTypedArraysBase
+{
typedef T element_type;
TypedArray_base()
@@ -72,7 +38,7 @@ struct TypedArray_base : public TypedArrayObjectStorage {
}
TypedArray_base(TypedArray_base&& aOther)
- : TypedArrayObjectStorage(Move(aOther)),
+ : SpiderMonkeyInterfaceObjectStorage(Move(aOther)),
mData(aOther.mData),
mLength(aOther.mLength),
mShared(aOther.mShared),
@@ -94,14 +60,10 @@ public:
inline bool Init(JSObject* obj)
{
MOZ_ASSERT(!inited());
- mTypedObj = mWrappedObj = UnwrapArray(obj);
+ mImplObj = mWrappedObj = UnwrapArray(obj);
return inited();
}
- inline bool inited() const {
- return !!mTypedObj;
- }
-
// About shared memory:
//
// Any DOM TypedArray as well as any DOM ArrayBufferView that does
@@ -173,22 +135,11 @@ public:
return mLength;
}
- inline JSObject *Obj() const {
- MOZ_ASSERT(inited());
- return mWrappedObj;
- }
-
- inline bool WrapIntoNewCompartment(JSContext* cx)
- {
- return JS_WrapObject(cx,
- JS::MutableHandle<JSObject*>::fromMarkedLocation(&mWrappedObj));
- }
-
inline void ComputeLengthAndData() const
{
MOZ_ASSERT(inited());
MOZ_ASSERT(!mComputed);
- GetLengthAndDataAndSharedness(mTypedObj, &mLength, &mShared, &mData);
+ GetLengthAndDataAndSharedness(mImplObj, &mLength, &mShared, &mData);
mComputed = true;
}
@@ -363,77 +314,6 @@ class TypedArrayCreator
const ArrayType& mArray;
};
-// A class for rooting an existing TypedArray struct
-template<typename ArrayType>
-class MOZ_RAII TypedArrayRooter : private JS::CustomAutoRooter
-{
-public:
- template <typename CX>
- TypedArrayRooter(const CX& cx,
- ArrayType* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
- JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
- mArray(aArray)
- {
- }
-
- virtual void trace(JSTracer* trc) override
- {
- mArray->TraceSelf(trc);
- }
-
-private:
- TypedArrayObjectStorage* const mArray;
-};
-
-// And a specialization for dealing with nullable typed arrays
-template<typename Inner> struct Nullable;
-template<typename ArrayType>
-class MOZ_RAII TypedArrayRooter<Nullable<ArrayType> > :
- private JS::CustomAutoRooter
-{
-public:
- template <typename CX>
- TypedArrayRooter(const CX& cx,
- Nullable<ArrayType>* aArray MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
- JS::CustomAutoRooter(cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT),
- mArray(aArray)
- {
- }
-
- virtual void trace(JSTracer* trc) override
- {
- if (!mArray->IsNull()) {
- mArray->Value().TraceSelf(trc);
- }
- }
-
-private:
- Nullable<ArrayType>* const mArray;
-};
-
-// Class for easily setting up a rooted typed array object on the stack
-template<typename ArrayType>
-class MOZ_RAII RootedTypedArray final : public ArrayType,
- private TypedArrayRooter<ArrayType>
-{
-public:
- template <typename CX>
- explicit RootedTypedArray(const CX& cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
- ArrayType(),
- TypedArrayRooter<ArrayType>(cx, this
- MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
- {
- }
-
- template <typename CX>
- RootedTypedArray(const CX& cx, JSObject* obj MOZ_GUARD_OBJECT_NOTIFIER_PARAM) :
- ArrayType(obj),
- TypedArrayRooter<ArrayType>(cx, this
- MOZ_GUARD_OBJECT_NOTIFIER_PARAM_TO_PARENT)
- {
- }
-};
-
} // namespace dom
} // namespace mozilla
diff --git a/dom/bindings/moz.build b/dom/bindings/moz.build
index 9afaf4fd45..60309080e4 100644
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -38,9 +38,11 @@ EXPORTS.mozilla.dom += [
'NonRefcountedDOMObject.h',
'Nullable.h',
'PrimitiveConversions.h',
+ 'ReadableStream.h',
'Record.h',
'RootedDictionary.h',
'SimpleGlobalObject.h',
+ 'SpiderMonkeyInterface.h',
'StructuredClone.h',
'ToJSValue.h',
'TypedArray.h',
diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py
index 59db43f6bd..0aa3afa4e8 100644
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -2054,6 +2054,9 @@ class IDLType(IDLObject):
def isRecord(self):
return False
+ def isReadableStream(self):
+ return False
+
def isArrayBuffer(self):
return False
@@ -2081,12 +2084,12 @@ class IDLType(IDLObject):
def isSpiderMonkeyInterface(self):
""" Returns a boolean indicating whether this type is an 'interface'
- type that is implemented in Spidermonkey. At the moment, this
- only returns true for the types from the TypedArray spec. """
+ type that is implemented in SpiderMonkey. """
return self.isInterface() and (self.isArrayBuffer() or
self.isArrayBufferView() or
self.isSharedArrayBuffer() or
- self.isTypedArray())
+ self.isTypedArray() or
+ self.isReadableStream())
def isDictionary(self):
return False
@@ -2275,6 +2278,9 @@ class IDLNullableType(IDLParameterizedType):
def isRecord(self):
return self.inner.isRecord()
+ def isReadableStream(self):
+ return self.inner.isReadableStream()
+
def isArrayBuffer(self):
return self.inner.isArrayBuffer()
@@ -2634,6 +2640,9 @@ class IDLTypedefType(IDLType):
def isRecord(self):
return self.inner.isRecord()
+ def isReadableStream(self):
+ return self.inner.isReadableStream()
+
def isDictionary(self):
return self.inner.isDictionary()
@@ -2945,7 +2954,8 @@ class IDLBuiltinType(IDLType):
'Int32Array',
'Uint32Array',
'Float32Array',
- 'Float64Array'
+ 'Float64Array',
+ 'ReadableStream',
)
TagLookup = {
@@ -2980,7 +2990,8 @@ class IDLBuiltinType(IDLType):
Types.Int32Array: IDLType.Tags.interface,
Types.Uint32Array: IDLType.Tags.interface,
Types.Float32Array: IDLType.Tags.interface,
- Types.Float64Array: IDLType.Tags.interface
+ Types.Float64Array: IDLType.Tags.interface,
+ Types.ReadableStream: IDLType.Tags.interface,
}
def __init__(self, location, name, type):
@@ -3027,6 +3038,9 @@ class IDLBuiltinType(IDLType):
return (self._typeTag >= IDLBuiltinType.Types.Int8Array and
self._typeTag <= IDLBuiltinType.Types.Float64Array)
+ def isReadableStream(self):
+ return self._typeTag == IDLBuiltinType.Types.ReadableStream
+
def isInterface(self):
# TypedArray things are interface types per the TypedArray spec,
# but we handle them as builtins because SpiderMonkey implements
@@ -3034,7 +3048,8 @@ class IDLBuiltinType(IDLType):
return (self.isArrayBuffer() or
self.isArrayBufferView() or
self.isSharedArrayBuffer() or
- self.isTypedArray())
+ self.isTypedArray() or
+ self.isReadableStream())
def isNonCallbackInterface(self):
# All the interfaces we can be are non-callback
@@ -3104,6 +3119,7 @@ class IDLBuiltinType(IDLType):
# that's not an ArrayBuffer or a callback interface
(self.isArrayBuffer() and not other.isArrayBuffer()) or
(self.isSharedArrayBuffer() and not other.isSharedArrayBuffer()) or
+ (self.isReadableStream() and not other.isReadableStream()) or
# ArrayBufferView is distinguishable from everything
# that's not an ArrayBufferView or typed array.
(self.isArrayBufferView() and not other.isArrayBufferView() and
@@ -3213,7 +3229,10 @@ BuiltinTypes = {
IDLBuiltinType.Types.Float32Array),
IDLBuiltinType.Types.Float64Array:
IDLBuiltinType(BuiltinLocation("<builtin type>"), "Float64Array",
- IDLBuiltinType.Types.Float64Array)
+ IDLBuiltinType.Types.Float64Array),
+ IDLBuiltinType.Types.ReadableStream:
+ IDLBuiltinType(BuiltinLocation("<builtin type>"), "ReadableStream",
+ IDLBuiltinType.Types.ReadableStream),
}
@@ -5232,7 +5251,8 @@ class Tokenizer(object):
"maplike": "MAPLIKE",
"setlike": "SETLIKE",
"iterable": "ITERABLE",
- "namespace": "NAMESPACE"
+ "namespace": "NAMESPACE",
+ "ReadableStream": "READABLESTREAM",
}
tokens.extend(keywords.values())
@@ -6420,6 +6440,7 @@ class Parser(Tokenizer):
NonAnyType : PrimitiveType Null
| ARRAYBUFFER Null
| SHAREDARRAYBUFFER Null
+ | READABLESTREAM Null
| OBJECT Null
"""
if p[1] == "object":
@@ -6428,6 +6449,8 @@ class Parser(Tokenizer):
type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer]
elif p[1] == "SharedArrayBuffer":
type = BuiltinTypes[IDLBuiltinType.Types.SharedArrayBuffer]
+ elif p[1] == "ReadableStream":
+ type = BuiltinTypes[IDLBuiltinType.Types.ReadableStream]
else:
type = BuiltinTypes[p[1]]
diff --git a/dom/cache/AutoUtils.cpp b/dom/cache/AutoUtils.cpp
index d1f354336e..bfa4cffd23 100644
--- a/dom/cache/AutoUtils.cpp
+++ b/dom/cache/AutoUtils.cpp
@@ -289,9 +289,9 @@ MatchInPutList(InternalRequest* aRequest,
} // namespace
void
-AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
- SchemeAction aSchemeAction, Response& aResponse,
- ErrorResult& aRv)
+AutoChildOpArgs::Add(JSContext* aCx, InternalRequest* aRequest,
+ BodyAction aBodyAction, SchemeAction aSchemeAction,
+ Response& aResponse, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(!mSent);
@@ -329,7 +329,7 @@ AutoChildOpArgs::Add(InternalRequest* aRequest, BodyAction aBodyAction,
mTypeUtils->ToCacheRequest(pair.request(), aRequest, aBodyAction,
aSchemeAction, mStreamCleanupList, aRv);
if (!aRv.Failed()) {
- mTypeUtils->ToCacheResponse(pair.response(), aResponse,
+ mTypeUtils->ToCacheResponse(aCx, pair.response(), aResponse,
mStreamCleanupList, aRv);
}
diff --git a/dom/cache/AutoUtils.h b/dom/cache/AutoUtils.h
index 244639f7c0..188c9d74e3 100644
--- a/dom/cache/AutoUtils.h
+++ b/dom/cache/AutoUtils.h
@@ -55,7 +55,7 @@ public:
void Add(InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, ErrorResult& aRv);
- void Add(InternalRequest* aRequest, BodyAction aBodyAction,
+ void Add(JSContext* aCx, InternalRequest* aRequest, BodyAction aBodyAction,
SchemeAction aSchemeAction, Response& aResponse, ErrorResult& aRv);
const CacheOpArgs& SendAsOpArgs();
diff --git a/dom/cache/Cache.cpp b/dom/cache/Cache.cpp
index 60e4f76b92..dd142881ec 100644
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -184,7 +184,10 @@ public:
// Now store the unwrapped Response list in the Cache.
ErrorResult result;
- RefPtr<Promise> put = mCache->PutAll(mRequestList, responseList, result);
+ // TODO: Here we use the JSContext as received by the ResolvedCallback, and
+ // its state could be the wrong one. The spec doesn't say anything
+ // about it, yet (bug 1384006)
+ RefPtr<Promise> put = mCache->PutAll(aCx, mRequestList, responseList, result);
if (NS_WARN_IF(result.Failed())) {
// TODO: abort the fetch requests we have running (bug 1157434)
mPromise->MaybeReject(result);
@@ -245,7 +248,7 @@ Cache::Cache(nsIGlobalObject* aGlobal, CacheChild* aActor)
}
already_AddRefed<Promise>
-Cache::Match(const RequestOrUSVString& aRequest,
+Cache::Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@@ -255,7 +258,8 @@ Cache::Match(const RequestOrUSVString& aRequest,
CacheChild::AutoLock actorLock(mActor);
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
+ RefPtr<InternalRequest> ir =
+ ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@@ -274,7 +278,7 @@ Cache::Match(const RequestOrUSVString& aRequest,
}
already_AddRefed<Promise>
-Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
+Cache::MatchAll(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@@ -290,8 +294,8 @@ Cache::MatchAll(const Optional<RequestOrUSVString>& aRequest,
AutoChildOpArgs args(this, CacheMatchAllArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
- IgnoreBody, aRv);
+ RefPtr<InternalRequest> ir = ToInternalRequest(aCx, aRequest.Value(),
+ IgnoreBody, aRv);
if (aRv.Failed()) {
return nullptr;
}
@@ -390,8 +394,8 @@ Cache::AddAll(JSContext* aContext,
}
already_AddRefed<Promise>
-Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
- ErrorResult& aRv)
+Cache::Put(JSContext* aCx, const RequestOrUSVString& aRequest,
+ Response& aResponse, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -404,14 +408,14 @@ Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
return nullptr;
}
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, ReadBody, aRv);
+ RefPtr<InternalRequest> ir = ToInternalRequest(aCx, aRequest, ReadBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
AutoChildOpArgs args(this, CachePutAllArgs(), 1);
- args.Add(ir, ReadBody, TypeErrorOnInvalidScheme,
+ args.Add(aCx, ir, ReadBody, TypeErrorOnInvalidScheme,
aResponse, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@@ -421,7 +425,7 @@ Cache::Put(const RequestOrUSVString& aRequest, Response& aResponse,
}
already_AddRefed<Promise>
-Cache::Delete(const RequestOrUSVString& aRequest,
+Cache::Delete(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@@ -431,7 +435,8 @@ Cache::Delete(const RequestOrUSVString& aRequest,
CacheChild::AutoLock actorLock(mActor);
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest, IgnoreBody, aRv);
+ RefPtr<InternalRequest> ir =
+ ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@@ -450,7 +455,7 @@ Cache::Delete(const RequestOrUSVString& aRequest,
}
already_AddRefed<Promise>
-Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
+Cache::Keys(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
if (NS_WARN_IF(!mActor)) {
@@ -466,8 +471,8 @@ Cache::Keys(const Optional<RequestOrUSVString>& aRequest,
AutoChildOpArgs args(this, CacheKeysArgs(void_t(), params), 1);
if (aRequest.WasPassed()) {
- RefPtr<InternalRequest> ir = ToInternalRequest(aRequest.Value(),
- IgnoreBody, aRv);
+ RefPtr<InternalRequest> ir =
+ ToInternalRequest(aCx, aRequest.Value(), IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@@ -627,7 +632,7 @@ Cache::AddAll(const GlobalObject& aGlobal,
}
already_AddRefed<Promise>
-Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
+Cache::PutAll(JSContext* aCx, const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv)
{
@@ -644,7 +649,8 @@ Cache::PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
for (uint32_t i = 0; i < aRequestList.Length(); ++i) {
RefPtr<InternalRequest> ir = aRequestList[i]->GetInternalRequest();
- args.Add(ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i], aRv);
+ args.Add(aCx, ir, ReadBody, TypeErrorOnInvalidScheme, *aResponseList[i],
+ aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
diff --git a/dom/cache/Cache.h b/dom/cache/Cache.h
index 04f891dca5..6fea3d36a0 100644
--- a/dom/cache/Cache.h
+++ b/dom/cache/Cache.h
@@ -43,10 +43,10 @@ public:
// webidl interface methods
already_AddRefed<Promise>
- Match(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
- ErrorResult& aRv);
+ Match(JSContext* aCx, const RequestOrUSVString& aRequest,
+ const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
- MatchAll(const Optional<RequestOrUSVString>& aRequest,
+ MatchAll(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
Add(JSContext* aContext, const RequestOrUSVString& aRequest,
@@ -55,13 +55,13 @@ public:
AddAll(JSContext* aContext,
const Sequence<OwningRequestOrUSVString>& aRequests, ErrorResult& aRv);
already_AddRefed<Promise>
- Put(const RequestOrUSVString& aRequest, Response& aResponse,
+ Put(JSContext* aCx, const RequestOrUSVString& aRequest, Response& aResponse,
ErrorResult& aRv);
already_AddRefed<Promise>
- Delete(const RequestOrUSVString& aRequest, const CacheQueryOptions& aOptions,
- ErrorResult& aRv);
+ Delete(JSContext* aCx, const RequestOrUSVString& aRequest,
+ const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise>
- Keys(const Optional<RequestOrUSVString>& aRequest,
+ Keys(JSContext* aCx, const Optional<RequestOrUSVString>& aRequest,
const CacheQueryOptions& aParams, ErrorResult& aRv);
// binding methods
@@ -100,7 +100,7 @@ private:
ErrorResult& aRv);
already_AddRefed<Promise>
- PutAll(const nsTArray<RefPtr<Request>>& aRequestList,
+ PutAll(JSContext* aCx, const nsTArray<RefPtr<Request>>& aRequestList,
const nsTArray<RefPtr<Response>>& aResponseList,
ErrorResult& aRv);
diff --git a/dom/cache/CacheOpChild.cpp b/dom/cache/CacheOpChild.cpp
index 492c205ffc..9dc8997c26 100644
--- a/dom/cache/CacheOpChild.cpp
+++ b/dom/cache/CacheOpChild.cpp
@@ -74,7 +74,11 @@ CacheOpChild::CacheOpChild(CacheWorkerHolder* aWorkerHolder,
MOZ_DIAGNOSTIC_ASSERT(mPromise);
MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerHolder);
- SetWorkerHolder(aWorkerHolder);
+ RefPtr<CacheWorkerHolder> workerHolder =
+ CacheWorkerHolder::PreferBehavior(aWorkerHolder,
+ CacheWorkerHolder::PreventIdleShutdownStart);
+
+ SetWorkerHolder(workerHolder);
}
CacheOpChild::~CacheOpChild()
@@ -165,7 +169,11 @@ CacheOpChild::Recv__delete__(const ErrorResult& aRv,
break;
}
- actor->SetWorkerHolder(GetWorkerHolder());
+ RefPtr<CacheWorkerHolder> workerHolder =
+ CacheWorkerHolder::PreferBehavior(GetWorkerHolder(),
+ CacheWorkerHolder::AllowIdleShutdownStart);
+
+ actor->SetWorkerHolder(workerHolder);
RefPtr<Cache> cache = new Cache(mGlobal, actor);
mPromise->MaybeResolve(cache);
break;
diff --git a/dom/cache/CacheStorage.cpp b/dom/cache/CacheStorage.cpp
index 2dae0b157d..05df8ddc69 100644
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -204,7 +204,8 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
}
RefPtr<CacheWorkerHolder> workerHolder =
- CacheWorkerHolder::Create(aWorkerPrivate);
+ CacheWorkerHolder::Create(aWorkerPrivate,
+ CacheWorkerHolder::AllowIdleShutdownStart);
if (!workerHolder) {
NS_WARNING("Worker thread is shutting down.");
aRv.Throw(NS_ERROR_FAILURE);
@@ -315,7 +316,7 @@ CacheStorage::CacheStorage(nsresult aFailureResult)
}
already_AddRefed<Promise>
-CacheStorage::Match(const RequestOrUSVString& aRequest,
+CacheStorage::Match(JSContext* aCx, const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions, ErrorResult& aRv)
{
NS_ASSERT_OWNINGTHREAD(CacheStorage);
@@ -325,8 +326,8 @@ CacheStorage::Match(const RequestOrUSVString& aRequest,
return nullptr;
}
- RefPtr<InternalRequest> request = ToInternalRequest(aRequest, IgnoreBody,
- aRv);
+ RefPtr<InternalRequest> request =
+ ToInternalRequest(aCx, aRequest, IgnoreBody, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
diff --git a/dom/cache/CacheStorage.h b/dom/cache/CacheStorage.h
index 04a2fa0dd9..c1cef4b428 100644
--- a/dom/cache/CacheStorage.h
+++ b/dom/cache/CacheStorage.h
@@ -59,9 +59,9 @@ public:
DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
// webidl interface methods
- already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
- const CacheQueryOptions& aOptions,
- ErrorResult& aRv);
+ already_AddRefed<Promise>
+ Match(JSContext* aCx, const RequestOrUSVString& aRequest,
+ const CacheQueryOptions& aOptions, ErrorResult& aRv);
already_AddRefed<Promise> Has(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Open(const nsAString& aKey, ErrorResult& aRv);
already_AddRefed<Promise> Delete(const nsAString& aKey, ErrorResult& aRv);
diff --git a/dom/cache/CacheWorkerHolder.cpp b/dom/cache/CacheWorkerHolder.cpp
index 4ac97cbcab..1fd78553ee 100644
--- a/dom/cache/CacheWorkerHolder.cpp
+++ b/dom/cache/CacheWorkerHolder.cpp
@@ -18,11 +18,11 @@ using mozilla::dom::workers::WorkerPrivate;
// static
already_AddRefed<CacheWorkerHolder>
-CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
+CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate, Behavior aBehavior)
{
MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
- RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder();
+ RefPtr<CacheWorkerHolder> workerHolder = new CacheWorkerHolder(aBehavior);
if (NS_WARN_IF(!workerHolder->HoldWorker(aWorkerPrivate, Terminating))) {
return nullptr;
}
@@ -30,6 +30,28 @@ CacheWorkerHolder::Create(WorkerPrivate* aWorkerPrivate)
return workerHolder.forget();
}
+// static
+already_AddRefed<CacheWorkerHolder>
+CacheWorkerHolder::PreferBehavior(CacheWorkerHolder* aCurrentHolder,
+ Behavior aBehavior)
+{
+ if (!aCurrentHolder) {
+ return nullptr;
+ }
+
+ RefPtr<CacheWorkerHolder> orig = aCurrentHolder;
+ if (orig->GetBehavior() == aBehavior) {
+ return orig.forget();
+ }
+
+ RefPtr<CacheWorkerHolder> replace = Create(orig->mWorkerPrivate, aBehavior);
+ if (!replace) {
+ return orig.forget();
+ }
+
+ return replace.forget();
+}
+
void
CacheWorkerHolder::AddActor(ActorChild* aActor)
{
@@ -88,8 +110,9 @@ CacheWorkerHolder::Notify(Status aStatus)
return true;
}
-CacheWorkerHolder::CacheWorkerHolder()
- : mNotified(false)
+CacheWorkerHolder::CacheWorkerHolder(Behavior aBehavior)
+ : WorkerHolder(aBehavior)
+ , mNotified(false)
{
}
diff --git a/dom/cache/CacheWorkerHolder.h b/dom/cache/CacheWorkerHolder.h
index 9eed7e2b6a..7e4c55f839 100644
--- a/dom/cache/CacheWorkerHolder.h
+++ b/dom/cache/CacheWorkerHolder.h
@@ -25,7 +25,11 @@ class CacheWorkerHolder final : public workers::WorkerHolder
{
public:
static already_AddRefed<CacheWorkerHolder>
- Create(workers::WorkerPrivate* aWorkerPrivate);
+ Create(workers::WorkerPrivate* aWorkerPrivate,
+ Behavior aBehavior);
+
+ static already_AddRefed<CacheWorkerHolder>
+ PreferBehavior(CacheWorkerHolder* aCurrentHolder, Behavior aBehavior);
void AddActor(ActorChild* aActor);
void RemoveActor(ActorChild* aActor);
@@ -36,7 +40,7 @@ public:
virtual bool Notify(workers::Status aStatus) override;
private:
- CacheWorkerHolder();
+ explicit CacheWorkerHolder(Behavior aBehavior);
~CacheWorkerHolder();
nsTArray<ActorChild*> mActorList;
diff --git a/dom/cache/TypeUtils.cpp b/dom/cache/TypeUtils.cpp
index f849f18874..1af5ee9458 100644
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -79,7 +79,7 @@ ToHeadersEntryList(nsTArray<HeadersEntry>& aOut, InternalHeaders* aHeaders)
} // namespace
already_AddRefed<InternalRequest>
-TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
+TypeUtils::ToInternalRequest(JSContext* aCx, const RequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
if (aIn.IsRequest()) {
@@ -87,7 +87,7 @@ TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
- CheckAndSetBodyUsed(&request, aBodyAction, aRv);
+ CheckAndSetBodyUsed(aCx, &request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request.GetInternalRequest();
@@ -97,7 +97,8 @@ TypeUtils::ToInternalRequest(const RequestOrUSVString& aIn,
}
already_AddRefed<InternalRequest>
-TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn,
+TypeUtils::ToInternalRequest(JSContext* aCx,
+ const OwningRequestOrUSVString& aIn,
BodyAction aBodyAction, ErrorResult& aRv)
{
@@ -106,7 +107,7 @@ TypeUtils::ToInternalRequest(const OwningRequestOrUSVString& aIn,
// Check and set bodyUsed flag immediately because its on Request
// instead of InternalRequest.
- CheckAndSetBodyUsed(request, aBodyAction, aRv);
+ CheckAndSetBodyUsed(aCx, request, aBodyAction, aRv);
if (aRv.Failed()) { return nullptr; }
return request->GetInternalRequest();
@@ -204,7 +205,7 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
}
void
-TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn,
+TypeUtils::ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv)
{
@@ -222,7 +223,10 @@ TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn,
nsCOMPtr<nsIInputStream> stream;
ir->GetUnfilteredBody(getter_AddRefs(stream));
if (stream) {
- aIn.SetBodyUsed();
+ aIn.SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
}
SerializeCacheStream(stream, &aOut.body(), aStreamCleanupList, aRv);
@@ -426,8 +430,8 @@ TypeUtils::ProcessURL(nsACString& aUrl, bool* aSchemeValidOut,
}
void
-TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
- ErrorResult& aRv)
+TypeUtils::CheckAndSetBodyUsed(JSContext* aCx, Request* aRequest,
+ BodyAction aBodyAction, ErrorResult& aRv)
{
MOZ_DIAGNOSTIC_ASSERT(aRequest);
@@ -443,7 +447,10 @@ TypeUtils::CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
nsCOMPtr<nsIInputStream> stream;
aRequest->GetBody(getter_AddRefs(stream));
if (stream) {
- aRequest->SetBodyUsed();
+ aRequest->SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
}
}
diff --git a/dom/cache/TypeUtils.h b/dom/cache/TypeUtils.h
index 274586e3f7..ff5816db3e 100644
--- a/dom/cache/TypeUtils.h
+++ b/dom/cache/TypeUtils.h
@@ -73,12 +73,12 @@ public:
GetIPCManager() = 0;
already_AddRefed<InternalRequest>
- ToInternalRequest(const RequestOrUSVString& aIn, BodyAction aBodyAction,
- ErrorResult& aRv);
+ ToInternalRequest(JSContext* aCx, const RequestOrUSVString& aIn,
+ BodyAction aBodyAction, ErrorResult& aRv);
already_AddRefed<InternalRequest>
- ToInternalRequest(const OwningRequestOrUSVString& aIn, BodyAction aBodyAction,
- ErrorResult& aRv);
+ ToInternalRequest(JSContext* aCx, const OwningRequestOrUSVString& aIn,
+ BodyAction aBodyAction, ErrorResult& aRv);
void
ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
@@ -91,7 +91,7 @@ public:
ErrorResult& aRv);
void
- ToCacheResponse(CacheResponse& aOut, Response& aIn,
+ ToCacheResponse(JSContext* aCx, CacheResponse& aOut, Response& aIn,
nsTArray<UniquePtr<mozilla::ipc::AutoIPCStream>>& aStreamCleanupList,
ErrorResult& aRv);
@@ -133,7 +133,7 @@ public:
private:
void
- CheckAndSetBodyUsed(Request* aRequest, BodyAction aBodyAction,
+ CheckAndSetBodyUsed(JSContext* aCx, Request* aRequest, BodyAction aBodyAction,
ErrorResult& aRv);
already_AddRefed<InternalRequest>
diff --git a/dom/canvas/CanvasRenderingContext2D.cpp b/dom/canvas/CanvasRenderingContext2D.cpp
index ed263f75d2..1e7eab82b2 100644
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2974,8 +2974,8 @@ ValidateRect(double& aX, double& aY, double& aWidth, double& aHeight, bool aIsZe
// The values of canvas API input are in double precision, but Moz2D APIs are
// using float precision. Bypass canvas API calls when the input is out of
// float precision to avoid precision problem
- if (!std::isfinite((float)aX) | !std::isfinite((float)aY) |
- !std::isfinite((float)aWidth) | !std::isfinite((float)aHeight)) {
+ if (!std::isfinite((float)aX) || !std::isfinite((float)aY) ||
+ !std::isfinite((float)aWidth) || !std::isfinite((float)aHeight)) {
return false;
}
@@ -5959,7 +5959,7 @@ void
CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
double aDy, ErrorResult& aError)
{
- RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);
@@ -5975,7 +5975,7 @@ CanvasRenderingContext2D::PutImageData(ImageData& aImageData, double aDx,
double aDirtyHeight,
ErrorResult& aError)
{
- RootedTypedArray<Uint8ClampedArray> arr(RootingCx());
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> arr(RootingCx());
DebugOnly<bool> inited = arr.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);
diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp
index 042c0fa11a..7aaf4e74d8 100644
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -929,7 +929,7 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, ImageData& aImageData,
const Maybe<IntRect>& aCropRect, ErrorResult& aRv)
{
// Copy data into SourceSurface.
- RootedTypedArray<Uint8ClampedArray> array(RootingCx());
+ RootedSpiderMonkeyInterface<Uint8ClampedArray> array(RootingCx());
DebugOnly<bool> inited = array.Init(aImageData.GetDataObject());
MOZ_ASSERT(inited);
diff --git a/dom/canvas/WebGLTextureUpload.cpp b/dom/canvas/WebGLTextureUpload.cpp
index ed199cfb43..661fb91ce2 100644
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -475,7 +475,7 @@ WebGLTexture::TexImage(const char* funcName, TexImageTarget target, GLint level,
GLsizei depth, GLint border, const webgl::PackingInfo& pi,
const TexImageSource& src)
{
- dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
+ dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
depth, border, pi, src, &scopedArr);
if (!blob)
@@ -491,7 +491,7 @@ WebGLTexture::TexSubImage(const char* funcName, TexImageTarget target, GLint lev
const webgl::PackingInfo& pi, const TexImageSource& src)
{
const GLint border = 0;
- dom::RootedTypedArray<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
+ dom::RootedSpiderMonkeyInterface<dom::Uint8ClampedArray> scopedArr(dom::RootingCx());
const auto blob = ValidateTexOrSubImage(mContext, funcName, target, width, height,
depth, border, pi, src, &scopedArr);
if (!blob)
diff --git a/dom/crypto/WebCryptoTask.cpp b/dom/crypto/WebCryptoTask.cpp
index ed47325f8d..34a4c877d7 100644
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -1366,7 +1366,7 @@ public:
mDataIsJwk = false;
// Try ArrayBuffer
- RootedTypedArray<ArrayBuffer> ab(aCx);
+ RootedSpiderMonkeyInterface<ArrayBuffer> ab(aCx);
if (ab.Init(aKeyData)) {
if (!mKeyData.Assign(ab)) {
mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
@@ -1375,7 +1375,7 @@ public:
}
// Try ArrayBufferView
- RootedTypedArray<ArrayBufferView> abv(aCx);
+ RootedSpiderMonkeyInterface<ArrayBufferView> abv(aCx);
if (abv.Init(aKeyData)) {
if (!mKeyData.Assign(abv)) {
mEarlyRv = NS_ERROR_DOM_OPERATION_ERR;
diff --git a/dom/fetch/BodyExtractor.cpp b/dom/fetch/BodyExtractor.cpp
new file mode 100644
index 0000000000..b840641214
--- /dev/null
+++ b/dom/fetch/BodyExtractor.cpp
@@ -0,0 +1,207 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BodyExtractor.h"
+#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/XMLHttpRequest.h"
+#include "nsContentUtils.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMSerializer.h"
+#include "nsIGlobalObject.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIStorageStream.h"
+#include "nsStringStream.h"
+#include "nsIUnicodeEncoder.h"
+
+namespace mozilla {
+namespace dom {
+
+static nsresult
+GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
+ nsIInputStream** aResult, uint64_t* aContentLength,
+ nsACString& aContentType, nsACString& aCharset)
+{
+ aContentType.SetIsVoid(true);
+ aCharset.Truncate();
+
+ *aContentLength = aDataLength;
+ const char* data = reinterpret_cast<const char*>(aData);
+
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
+ NS_ASSIGNMENT_COPY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ stream.forget(aResult);
+
+ return NS_OK;
+}
+
+template<> nsresult
+BodyExtractor<const ArrayBuffer>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ mBody->ComputeLengthAndData();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+ aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template<> nsresult
+BodyExtractor<const ArrayBufferView>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ mBody->ComputeLengthAndData();
+ return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
+ aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+template<> nsresult
+BodyExtractor<nsIDocument>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
+ NS_ENSURE_STATE(domdoc);
+ aCharset.AssignLiteral("UTF-8");
+
+ nsresult rv;
+ nsCOMPtr<nsIStorageStream> storStream;
+ rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> output;
+ rv = storStream->GetOutputStream(0, getter_AddRefs(output));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBody->IsHTMLDocument()) {
+ aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8");
+
+ nsString serialized;
+ if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString utf8Serialized;
+ if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ uint32_t written;
+ rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_ASSERT(written == utf8Serialized.Length());
+ } else {
+ aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8");
+
+ nsCOMPtr<nsIDOMSerializer> serializer =
+ do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Make sure to use the encoding we'll send
+ rv = serializer->SerializeToStream(domdoc, output, aCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ output->Close();
+
+ uint32_t length;
+ rv = storStream->GetLength(&length);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aContentLength = length;
+
+ rv = storStream->NewInputStream(0, aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+template<> nsresult
+BodyExtractor<const nsAString>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ nsCOMPtr<nsIUnicodeEncoder> encoder =
+ EncodingUtils::EncoderForEncoding("UTF-8");
+ if (!encoder) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t destBufferLen;
+ nsresult rv = encoder->GetMaxLength(mBody->BeginReading(), mBody->Length(),
+ &destBufferLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCString encoded;
+ if (!encoded.SetCapacity(destBufferLen, fallible)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ char* destBuffer = encoded.BeginWriting();
+ int32_t srcLen = (int32_t) mBody->Length();
+ int32_t outLen = destBufferLen;
+ rv = encoder->Convert(mBody->BeginReading(), &srcLen, destBuffer, &outLen);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(outLen <= destBufferLen);
+ encoded.SetLength(outLen);
+
+ rv = NS_NewCStringInputStream(aResult, encoded);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aContentLength = outLen;
+ aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8");
+ aCharset.AssignLiteral("UTF-8");
+ return NS_OK;
+}
+
+template<> nsresult
+BodyExtractor<nsIInputStream>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ aContentTypeWithCharset.AssignLiteral("text/plain");
+ aCharset.Truncate();
+
+ nsresult rv = mBody->Available(aContentLength);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream(mBody);
+ stream.forget(aResult);
+ return NS_OK;
+}
+
+template<> nsresult
+BodyExtractor<nsIXHRSendable>::GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const
+{
+ return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
+ aCharset);
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/fetch/BodyExtractor.h b/dom/fetch/BodyExtractor.h
new file mode 100644
index 0000000000..73dfef6aff
--- /dev/null
+++ b/dom/fetch/BodyExtractor.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_BodyExtractor_h
+#define mozilla_dom_BodyExtractor_h
+
+#include "jsapi.h"
+#include "nsString.h"
+
+class nsIInputStream;
+class nsIGlobalObject;
+
+namespace mozilla {
+namespace dom {
+
+class BodyExtractorBase
+{
+public:
+ virtual nsresult GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const = 0;
+};
+
+// The implementation versions of this template are:
+// ArrayBuffer, ArrayBufferView, nsIXHRSendable (Blob, FormData,
+// URLSearchParams), nsAString, nsIDocument, nsIInputStream.
+template<typename Type>
+class BodyExtractor final : public BodyExtractorBase
+{
+ Type* mBody;
+public:
+ explicit BodyExtractor(Type* aBody) : mBody(aBody)
+ {}
+
+ nsresult GetAsStream(nsIInputStream** aResult,
+ uint64_t* aContentLength,
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset) const override;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_BodyExtractor_h
diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp
index 6a6b4faaf8..a9d8514765 100644
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -5,13 +5,13 @@
#include "Fetch.h"
#include "FetchConsumer.h"
+#include "FetchStream.h"
#include "nsIDocument.h"
#include "nsIGlobalObject.h"
#include "nsIStreamLoader.h"
#include "nsIThreadRetargetableRequest.h"
#include "nsIUnicodeDecoder.h"
-#include "nsIUnicodeEncoder.h"
#include "nsDOMString.h"
#include "nsNetUtil.h"
@@ -22,7 +22,7 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BodyUtil.h"
-#include "mozilla/dom/EncodingUtils.h"
+#include "mozilla/dom/DOMError.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/FetchDriver.h"
#include "mozilla/dom/File.h"
@@ -35,8 +35,10 @@
#include "mozilla/dom/Response.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/URLSearchParams.h"
+#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/workers/ServiceWorkerManager.h"
+#include "BodyExtractor.h"
#include "FetchObserver.h"
#include "InternalRequest.h"
#include "InternalResponse.h"
@@ -722,154 +724,95 @@ WorkerFetchResolver::FlushConsoleReport()
mReporter->FlushConsoleReports(worker->GetDocument());
}
-namespace {
-
-nsresult
-ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
- nsIInputStream** aStream,
- uint64_t& aContentLength)
-{
- aBuffer.ComputeLengthAndData();
- aContentLength = aBuffer.Length();
- //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
- return NS_NewByteInputStream(aStream,
- reinterpret_cast<char*>(aBuffer.Data()),
- aBuffer.Length(), NS_ASSIGNMENT_COPY);
-}
-
nsresult
-ExtractFromArrayBufferView(const ArrayBufferView& aBuffer,
- nsIInputStream** aStream,
- uint64_t& aContentLength)
+ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentTypeWithCharset,
+ uint64_t& aContentLength)
{
- aBuffer.ComputeLengthAndData();
- aContentLength = aBuffer.Length();
- //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
- return NS_NewByteInputStream(aStream,
- reinterpret_cast<char*>(aBuffer.Data()),
- aBuffer.Length(), NS_ASSIGNMENT_COPY);
-}
+ MOZ_ASSERT(aStream);
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
-nsresult
-ExtractFromBlob(const Blob& aBlob,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength)
-{
- RefPtr<BlobImpl> impl = aBlob.Impl();
- ErrorResult rv;
- aContentLength = impl->GetSize(rv);
- if (NS_WARN_IF(rv.Failed())) {
- return rv.StealNSResult();
+ if (aBodyInit.IsArrayBuffer()) {
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
-
- impl->GetInternalStream(aStream, rv);
- if (NS_WARN_IF(rv.Failed())) {
- return rv.StealNSResult();
+ if (aBodyInit.IsArrayBufferView()) {
+ BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
-
- nsAutoString type;
- impl->GetType(type);
- aContentType = NS_ConvertUTF16toUTF8(type);
- return NS_OK;
-}
-
-nsresult
-ExtractFromFormData(FormData& aFormData,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength)
-{
- nsAutoCString unusedCharset;
- return aFormData.GetSendInfo(aStream, &aContentLength,
- aContentType, unusedCharset);
-}
-
-nsresult
-ExtractFromUSVString(const nsString& aStr,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength)
-{
- nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
- if (!encoder) {
- return NS_ERROR_OUT_OF_MEMORY;
+ if (aBodyInit.IsBlob()) {
+ Blob& blob = aBodyInit.GetAsBlob();
+ BodyExtractor<nsIXHRSendable> body(&blob);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
-
- int32_t destBufferLen;
- nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
+ if (aBodyInit.IsFormData()) {
+ FormData& formData = aBodyInit.GetAsFormData();
+ BodyExtractor<nsIXHRSendable> body(&formData);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
-
- nsCString encoded;
- if (!encoded.SetCapacity(destBufferLen, fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
+ if (aBodyInit.IsUSVString()) {
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
-
- char* destBuffer = encoded.BeginWriting();
- int32_t srcLen = (int32_t) aStr.Length();
- int32_t outLen = destBufferLen;
- rv = encoder->Convert(aStr.get(), &srcLen, destBuffer, &outLen);
- if (NS_WARN_IF(NS_FAILED(rv))) {
- return rv;
+ if (aBodyInit.IsURLSearchParams()) {
+ URLSearchParams& usp = aBodyInit.GetAsURLSearchParams();
+ BodyExtractor<nsIXHRSendable> body(&usp);
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
- MOZ_ASSERT(outLen <= destBufferLen);
- encoded.SetLength(outLen);
-
- aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8");
- aContentLength = outLen;
-
- return NS_NewCStringInputStream(aStream, encoded);
-}
-
-nsresult
-ExtractFromURLSearchParams(const URLSearchParams& aParams,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength)
-{
- nsAutoString serialized;
- aParams.Stringify(serialized);
- aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
- aContentLength = serialized.Length();
- return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized));
+ NS_NOTREACHED("Should never reach here");
+ return NS_ERROR_FAILURE;
}
-} // namespace
nsresult
-ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
+ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
nsIInputStream** aStream,
- nsCString& aContentType,
+ nsCString& aContentTypeWithCharset,
uint64_t& aContentLength)
{
MOZ_ASSERT(aStream);
+ MOZ_ASSERT(!*aStream);
+
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
if (aBodyInit.IsArrayBuffer()) {
- const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
- return ExtractFromArrayBuffer(buf, aStream, aContentLength);
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
if (aBodyInit.IsArrayBufferView()) {
- const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
- return ExtractFromArrayBufferView(buf, aStream, aContentLength);
+ BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
if (aBodyInit.IsBlob()) {
- const Blob& blob = aBodyInit.GetAsBlob();
- return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
if (aBodyInit.IsFormData()) {
- FormData& form = aBodyInit.GetAsFormData();
- return ExtractFromFormData(form, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
if (aBodyInit.IsUSVString()) {
- nsAutoString str;
- str.Assign(aBodyInit.GetAsUSVString());
- return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
if (aBodyInit.IsURLSearchParams()) {
- URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
- return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
NS_NOTREACHED("Should never reach here");
@@ -877,69 +820,181 @@ ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDa
}
nsresult
-ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
+ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
nsIInputStream** aStream,
- nsCString& aContentType,
+ nsCString& aContentTypeWithCharset,
uint64_t& aContentLength)
{
MOZ_ASSERT(aStream);
MOZ_ASSERT(!*aStream);
+ // ReadableStreams should be handled by
+ // BodyExtractorReadableStream::GetAsStream.
+ MOZ_ASSERT(!aBodyInit.IsReadableStream());
+
+ nsAutoCString charset;
+ aContentTypeWithCharset.SetIsVoid(true);
+
if (aBodyInit.IsArrayBuffer()) {
- const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer();
- return ExtractFromArrayBuffer(buf, aStream, aContentLength);
+ BodyExtractor<const ArrayBuffer> body(&aBodyInit.GetAsArrayBuffer());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
+
if (aBodyInit.IsArrayBufferView()) {
- const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView();
- return ExtractFromArrayBufferView(buf, aStream, aContentLength);
+ BodyExtractor<const ArrayBufferView> body(&aBodyInit.GetAsArrayBufferView());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
+
if (aBodyInit.IsBlob()) {
- const Blob& blob = aBodyInit.GetAsBlob();
- return ExtractFromBlob(blob, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsBlob());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
+
if (aBodyInit.IsFormData()) {
- FormData& form = aBodyInit.GetAsFormData();
- return ExtractFromFormData(form, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsFormData());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
+
if (aBodyInit.IsUSVString()) {
- nsAutoString str;
- str.Assign(aBodyInit.GetAsUSVString());
- return ExtractFromUSVString(str, aStream, aContentType, aContentLength);
+ BodyExtractor<const nsAString> body(&aBodyInit.GetAsUSVString());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
+
if (aBodyInit.IsURLSearchParams()) {
- URLSearchParams& params = aBodyInit.GetAsURLSearchParams();
- return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength);
+ BodyExtractor<nsIXHRSendable> body(&aBodyInit.GetAsURLSearchParams());
+ return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
+ charset);
}
NS_NOTREACHED("Should never reach here");
return NS_ERROR_FAILURE;
}
+
template <class Derived>
-FetchBody<Derived>::FetchBody()
+FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
: mWorkerPrivate(nullptr)
+ , mOwner(aOwner)
+ , mReadableStreamBody(nullptr)
+ , mReadableStreamReader(nullptr)
, mBodyUsed(false)
{
+ MOZ_ASSERT(aOwner);
+
if (!NS_IsMainThread()) {
mWorkerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(mWorkerPrivate);
+ } else {
+ mWorkerPrivate = nullptr;
}
}
template
-FetchBody<Request>::FetchBody();
+FetchBody<Request>::FetchBody(nsIGlobalObject* aOwner);
template
-FetchBody<Response>::FetchBody();
+FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
template <class Derived>
FetchBody<Derived>::~FetchBody()
{
}
+template
+FetchBody<Request>::~FetchBody();
+
+template
+FetchBody<Response>::~FetchBody();
+
+template <class Derived>
+bool
+FetchBody<Derived>::BodyUsed() const
+{
+ if (mBodyUsed) {
+ return true;
+ }
+
+ // If this object is disturbed or locked, return false.
+ if (mReadableStreamBody) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mOwner)) {
+ return true;
+ }
+
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> body(cx, mReadableStreamBody);
+ if (JS::ReadableStreamIsDisturbed(body) ||
+ JS::ReadableStreamIsLocked(body) ||
+ !JS::ReadableStreamIsReadable(body)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template
+bool
+FetchBody<Request>::BodyUsed() const;
+
+template
+bool
+FetchBody<Response>::BodyUsed() const;
+
+template <class Derived>
+void
+FetchBody<Derived>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv)
+{
+ MOZ_ASSERT(aCx);
+
+ if (mBodyUsed) {
+ return;
+ }
+
+ mBodyUsed = true;
+
+ // If we already have a ReadableStreamBody and it has been created by DOM, we
+ // have to lock it now because it can have been shared with other objects.
+ if (mReadableStreamBody) {
+ JS::Rooted<JSObject*> readableStreamObj(aCx, mReadableStreamBody);
+ if (JS::ReadableStreamGetMode(readableStreamObj) ==
+ JS::ReadableStreamMode::ExternalSource) {
+ LockStream(aCx, readableStreamObj, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ } else {
+ // If this is not a native ReadableStream, let's activate the
+ // FetchStreamReader.
+ MOZ_ASSERT(mFetchStreamReader);
+ JS::Rooted<JSObject*> reader(aCx);
+ mFetchStreamReader->StartConsuming(aCx, readableStreamObj, &reader, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ mReadableStreamReader = reader;
+ }
+ }
+}
+
+template
+void
+FetchBody<Request>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
+
+template
+void
+FetchBody<Response>::SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
+
template <class Derived>
already_AddRefed<Promise>
-FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
+FetchBody<Derived>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv)
{
RefPtr<AbortSignal> signal = DerivedClass()->GetSignal();
if (signal && signal->Aborted()) {
@@ -952,11 +1007,15 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
return nullptr;
}
- SetBodyUsed();
+ SetBodyUsed(aCx, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = DerivedClass()->GetParentObject();
RefPtr<Promise> promise =
- FetchBodyConsumer<Derived>::Create(DerivedClass()->GetParentObject(),
- this, signal, aType, aRv);
+ FetchBodyConsumer<Derived>::Create(global, this, signal, aType, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
@@ -966,11 +1025,11 @@ FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
template
already_AddRefed<Promise>
-FetchBody<Request>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
+FetchBody<Request>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
template
already_AddRefed<Promise>
-FetchBody<Response>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
+FetchBody<Response>::ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
template <class Derived>
void
@@ -1000,5 +1059,170 @@ template
void
FetchBody<Response>::SetMimeType();
+template <class Derived>
+void
+FetchBody<Derived>::SetReadableStreamBody(JSObject* aBody)
+{
+ MOZ_ASSERT(!mReadableStreamBody);
+ MOZ_ASSERT(aBody);
+ mReadableStreamBody = aBody;
+}
+
+template
+void
+FetchBody<Request>::SetReadableStreamBody(JSObject* aBody);
+
+template
+void
+FetchBody<Response>::SetReadableStreamBody(JSObject* aBody);
+
+template <class Derived>
+void
+FetchBody<Derived>::GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ ErrorResult& aRv)
+{
+ if (mReadableStreamBody) {
+ aBodyOut.set(mReadableStreamBody);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ DerivedClass()->GetBody(getter_AddRefs(inputStream));
+
+ if (!inputStream) {
+ aBodyOut.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JSObject*> body(aCx);
+ FetchStream::Create(aCx, this, DerivedClass()->GetParentObject(),
+ inputStream, &body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(body);
+
+ // If the body has been already consumed, we lock the stream.
+ if (BodyUsed()) {
+ LockStream(aCx, body, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ mReadableStreamBody = body;
+ aBodyOut.set(mReadableStreamBody);
+}
+
+template
+void
+FetchBody<Request>::GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv);
+
+template
+void
+FetchBody<Response>::GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aMessage,
+ ErrorResult& aRv);
+
+template <class Derived>
+void
+FetchBody<Derived>::LockStream(JSContext* aCx,
+ JS::HandleObject aStream,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(JS::ReadableStreamGetMode(aStream) ==
+ JS::ReadableStreamMode::ExternalSource);
+
+ // This is native stream, creating a reader will not execute any JS code.
+ JS::Rooted<JSObject*> reader(aCx,
+ JS::ReadableStreamGetReader(aCx, aStream,
+ JS::ReadableStreamReaderMode::Default));
+ if (!reader) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mReadableStreamReader = reader;
+}
+
+template
+void
+FetchBody<Request>::LockStream(JSContext* aCx,
+ JS::HandleObject aStream,
+ ErrorResult& aRv);
+
+template
+void
+FetchBody<Response>::LockStream(JSContext* aCx,
+ JS::HandleObject aStream,
+ ErrorResult& aRv);
+
+template <class Derived>
+void
+FetchBody<Derived>::MaybeTeeReadableStreamBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
+ ErrorResult& aRv)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aStreamReader);
+ MOZ_DIAGNOSTIC_ASSERT(aInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(!BodyUsed());
+
+ aBodyOut.set(nullptr);
+ *aStreamReader = nullptr;
+ *aInputStream = nullptr;
+
+ if (!mReadableStreamBody) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> stream(aCx, mReadableStreamBody);
+
+ // If this is a ReadableStream with an external source, this has been
+ // generated by a Fetch. In this case, Fetch will be able to recreate it
+ // again when GetBody() is called.
+ if (JS::ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource) {
+ aBodyOut.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JSObject*> branch1(aCx);
+ JS::Rooted<JSObject*> branch2(aCx);
+
+ if (!JS::ReadableStreamTee(aCx, stream, &branch1, &branch2)) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ mReadableStreamBody = branch1;
+ aBodyOut.set(branch2);
+
+ aRv = FetchStreamReader::Create(aCx, mOwner, aStreamReader, aInputStream);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+template
+void
+FetchBody<Request>::MaybeTeeReadableStreamBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aMessage,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+template
+void
+FetchBody<Response>::MaybeTeeReadableStreamBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aMessage,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
} // namespace dom
} // namespace mozilla
diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h
index 73d5c15199..19b579cb6c 100644
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -1,207 +1,312 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_Fetch_h
-#define mozilla_dom_Fetch_h
-
-#include "nsAutoPtr.h"
-#include "nsIStreamLoader.h"
-
-#include "nsCOMPtr.h"
-#include "nsError.h"
-#include "nsProxyRelease.h"
-#include "nsString.h"
-
-#include "mozilla/DebugOnly.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/dom/Promise.h"
-#include "mozilla/dom/RequestBinding.h"
-
-class nsIGlobalObject;
-
-namespace mozilla {
-namespace dom {
-
-class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
-class BlobImpl;
-class InternalRequest;
-class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
-class RequestOrUSVString;
-
-namespace workers {
-class WorkerPrivate;
-} // namespace workers
-
-already_AddRefed<Promise>
-FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
- const RequestInit& aInit, ErrorResult& aRv);
-
-nsresult
-UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
-
-/*
- * Creates an nsIInputStream based on the fetch specifications 'extract a byte
- * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
- * Stores content type in out param aContentType.
- */
-nsresult
-ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength);
-
-/*
- * Non-owning version.
- */
-nsresult
-ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
- nsIInputStream** aStream,
- nsCString& aContentType,
- uint64_t& aContentLength);
-
-template <class Derived> class FetchBodyConsumer;
-
-enum FetchConsumeType
-{
- CONSUME_ARRAYBUFFER,
- CONSUME_BLOB,
- CONSUME_FORMDATA,
- CONSUME_JSON,
- CONSUME_TEXT,
-};
-
-/*
- * FetchBody's body consumption uses nsIInputStreamPump to read from the
- * underlying stream to a block of memory, which is then adopted by
- * ContinueConsumeBody() and converted to the right type based on the JS
- * function called.
- *
- * Use of the nsIInputStreamPump complicates things on the worker thread.
- * The solution used here is similar to WebSockets.
- * The difference is that we are only interested in completion and not data
- * events, and nsIInputStreamPump can only deliver completion on the main thread.
- *
- * Before starting the pump on the main thread, we addref the FetchBody to keep
- * it alive. Then we add a feature, to track the status of the worker.
- *
- * ContinueConsumeBody() is the function that cleans things up in both success
- * and error conditions and so all callers call it with the appropriate status.
- *
- * Once the read is initiated on the main thread there are two possibilities.
- *
- * 1) Pump finishes before worker has finished Running.
- * In this case we adopt the data and dispatch a runnable to the worker,
- * which derefs FetchBody and removes the feature and resolves the Promise.
- *
- * 2) Pump still working while worker has stopped Running.
- * The feature is Notify()ed and ContinueConsumeBody() is called with
- * NS_BINDING_ABORTED. We first Cancel() the pump using a sync runnable to
- * ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly
- * held by it) until pump->Cancel() is called. OnStreamComplete() will not
- * do anything if the error code is NS_BINDING_ABORTED, so we don't have to
- * worry about keeping anything alive.
- *
- * The pump is always released on the main thread.
- */
-template <class Derived>
-class FetchBody
-{
-public:
- friend class FetchBodyConsumer<Derived>;
-
- NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
- NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
-
- bool
- BodyUsed() const { return mBodyUsed; }
-
- already_AddRefed<Promise>
- ArrayBuffer(ErrorResult& aRv)
- {
- return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
- }
-
- already_AddRefed<Promise>
- Blob(ErrorResult& aRv)
- {
- return ConsumeBody(CONSUME_BLOB, aRv);
- }
-
- already_AddRefed<Promise>
- FormData(ErrorResult& aRv)
- {
- return ConsumeBody(CONSUME_FORMDATA, aRv);
- }
-
- already_AddRefed<Promise>
- Json(ErrorResult& aRv)
- {
- return ConsumeBody(CONSUME_JSON, aRv);
- }
-
- already_AddRefed<Promise>
- Text(ErrorResult& aRv)
- {
- return ConsumeBody(CONSUME_TEXT, aRv);
- }
-
- // Utility public methods accessed by various runnables.
-
- void
- SetBodyUsed()
- {
- mBodyUsed = true;
- }
-
- const nsCString&
- MimeType() const
- {
- return mMimeType;
- }
-
- virtual AbortSignal*
- GetSignal() const = 0;
-
-protected:
- FetchBody();
-
- // Always set whenever the FetchBody is created on the worker thread.
- workers::WorkerPrivate* mWorkerPrivate;
-
- virtual ~FetchBody();
-
- void
- SetMimeType();
-private:
- Derived*
- DerivedClass() const
- {
- return static_cast<Derived*>(const_cast<FetchBody*>(this));
- }
-
- already_AddRefed<Promise>
- ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
-
- bool
- IsOnTargetThread()
- {
- return NS_IsMainThread() == !mWorkerPrivate;
- }
-
- void
- AssertIsOnTargetThread()
- {
- MOZ_ASSERT(IsOnTargetThread());
- }
-
- // Only ever set once, always on target thread.
- bool mBodyUsed;
- nsCString mMimeType;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_Fetch_h
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Fetch_h
+#define mozilla_dom_Fetch_h
+
+#include "nsAutoPtr.h"
+#include "nsIStreamLoader.h"
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/FetchStreamReader.h"
+// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None
+#undef None
+#include "mozilla/dom/RequestBinding.h"
+
+class nsIGlobalObject;
+class nsIEventTarget;
+
+namespace mozilla {
+namespace dom {
+
+class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString;
+class BlobImpl;
+class InternalRequest;
+class OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
+struct ReadableStream;
+class RequestOrUSVString;
+
+namespace workers {
+class WorkerPrivate;
+} // namespace workers
+
+already_AddRefed<Promise>
+FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
+ const RequestInit& aInit, ErrorResult& aRv);
+
+nsresult
+UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
+
+/* Deal with unwieldy long webIDL-generated type names */
+namespace fetch {
+ typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString BodyInit;
+ typedef BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrReadableStreamOrUSVString ResponseBodyInit;
+ typedef OwningBlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString OwningBodyInit;
+};
+
+/*
+ * Creates an nsIInputStream based on the fetch specifications 'extract a byte
+ * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract.
+ * Stores content type in out param aContentType.
+ */
+nsresult
+ExtractByteStreamFromBody(const fetch::OwningBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+/*
+ * Non-owning version.
+ */
+nsresult
+ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+/*
+ * Non-owning version. This method should go away when BodyInit will contain
+ * ReadableStream.
+ */
+nsresult
+ExtractByteStreamFromBody(const fetch::ResponseBodyInit& aBodyInit,
+ nsIInputStream** aStream,
+ nsCString& aContentType,
+ uint64_t& aContentLength);
+
+template <class Derived> class FetchBodyConsumer;
+
+enum FetchConsumeType
+{
+ CONSUME_ARRAYBUFFER,
+ CONSUME_BLOB,
+ CONSUME_FORMDATA,
+ CONSUME_JSON,
+ CONSUME_TEXT,
+};
+
+class FetchStreamHolder
+{
+public:
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
+
+ virtual void NullifyStream() = 0;
+
+ virtual void MarkAsRead() = 0;
+
+ virtual JSObject* ReadableStreamBody() = 0;
+};
+
+/*
+ * FetchBody's body consumption uses nsIInputStreamPump to read from the
+ * underlying stream to a block of memory, which is then adopted by
+ * ContinueConsumeBody() and converted to the right type based on the JS
+ * function called.
+ *
+ * Use of the nsIInputStreamPump complicates things on the worker thread.
+ * The solution used here is similar to WebSockets.
+ * The difference is that we are only interested in completion and not data
+ * events, and nsIInputStreamPump can only deliver completion on the main thread.
+ *
+ * Before starting the pump on the main thread, we addref the FetchBody to keep
+ * it alive. Then we add a feature, to track the status of the worker.
+ *
+ * ContinueConsumeBody() is the function that cleans things up in both success
+ * and error conditions and so all callers call it with the appropriate status.
+ *
+ * Once the read is initiated on the main thread there are two possibilities.
+ *
+ * 1) Pump finishes before worker has finished Running.
+ * In this case we adopt the data and dispatch a runnable to the worker,
+ * which derefs FetchBody and removes the feature and resolves the Promise.
+ *
+ * 2) Pump still working while worker has stopped Running.
+ * The feature is Notify()ed and ContinueConsumeBody() is called with
+ * NS_BINDING_ABORTED. We first Cancel() the pump using a sync runnable to
+ * ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly
+ * held by it) until pump->Cancel() is called. OnStreamComplete() will not
+ * do anything if the error code is NS_BINDING_ABORTED, so we don't have to
+ * worry about keeping anything alive.
+ *
+ * The pump is always released on the main thread.
+ */
+template <class Derived>
+class FetchBody : public FetchStreamHolder
+{
+public:
+ friend class FetchBodyConsumer<Derived>;
+
+ bool
+ BodyUsed() const;
+
+ already_AddRefed<Promise>
+ ArrayBuffer(JSContext* aCx, ErrorResult& aRv)
+ {
+ return ConsumeBody(aCx, CONSUME_ARRAYBUFFER, aRv);
+ }
+
+ already_AddRefed<Promise>
+ Blob(JSContext* aCx, ErrorResult& aRv)
+ {
+ return ConsumeBody(aCx, CONSUME_BLOB, aRv);
+ }
+
+ already_AddRefed<Promise>
+ FormData(JSContext* aCx, ErrorResult& aRv)
+ {
+ return ConsumeBody(aCx, CONSUME_FORMDATA, aRv);
+ }
+
+ already_AddRefed<Promise>
+ Json(JSContext* aCx, ErrorResult& aRv)
+ {
+ return ConsumeBody(aCx, CONSUME_JSON, aRv);
+ }
+
+ already_AddRefed<Promise>
+ Text(JSContext* aCx, ErrorResult& aRv)
+ {
+ return ConsumeBody(aCx, CONSUME_TEXT, aRv);
+ }
+
+ void
+ GetBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ ErrorResult& aRv);
+
+ // If the body contains a ReadableStream body object, this method produces a
+ // tee() of it.
+ void
+ MaybeTeeReadableStreamBody(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aBodyOut,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream,
+ ErrorResult& aRv);
+
+ // Utility public methods accessed by various runnables.
+
+ // This method _must_ be called in order to set the body as used. If the body
+ // is a ReadableStream, this method will start reading the stream.
+ // More in details, this method does:
+ // 1) It uses an internal flag to track if the body is used. This is tracked
+ // separately from the ReadableStream disturbed state due to purely native
+ // streams.
+ // 2) If there is a ReadableStream reflector for the native stream it is
+ // Locked.
+ // 3) If there is a JS ReadableStream then we begin pumping it into the native
+ // body stream. This effectively locks and disturbs the stream.
+ //
+ // Note that JSContext is used only if there is a ReadableStream (this can
+ // happen because the body is a ReadableStream or because attribute body has
+ // already been used by content). If something goes wrong using
+ // ReadableStream, errors will be reported via ErrorResult and not as JS
+ // exceptions in JSContext. This is done in order to have a centralized error
+ // reporting way.
+ //
+ // Exceptions generated when reading from the ReadableStream are directly sent
+ // to the Console.
+ void
+ SetBodyUsed(JSContext* aCx, ErrorResult& aRv);
+
+ const nsCString&
+ MimeType() const
+ {
+ return mMimeType;
+ }
+
+ // FetchStreamHolder
+ void
+ NullifyStream() override
+ {
+ mReadableStreamBody = nullptr;
+ mReadableStreamReader = nullptr;
+ mFetchStreamReader = nullptr;
+ }
+
+ JSObject*
+ ReadableStreamBody() override
+ {
+ MOZ_ASSERT(mReadableStreamBody);
+ return mReadableStreamBody;
+ }
+
+ void
+ MarkAsRead() override
+ {
+ mBodyUsed = true;
+ }
+
+ virtual AbortSignal*
+ GetSignal() const = 0;
+
+protected:
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+ explicit FetchBody(nsIGlobalObject* aOwner);
+
+ // Always set whenever the FetchBody is created on the worker thread.
+ workers::WorkerPrivate* mWorkerPrivate;
+
+ // This is the ReadableStream exposed to content. Its underlying source is a FetchStream object.
+ JS::Heap<JSObject*> mReadableStreamBody;
+
+ // This is the Reader used to retrieve data from the body.
+ JS::Heap<JSObject*> mReadableStreamReader;
+ RefPtr<FetchStreamReader> mFetchStreamReader;
+
+ virtual ~FetchBody();
+
+ void
+ SetMimeType();
+
+ void
+ SetReadableStreamBody(JSObject* aBody);
+
+private:
+ Derived*
+ DerivedClass() const
+ {
+ return static_cast<Derived*>(const_cast<FetchBody*>(this));
+ }
+
+ already_AddRefed<Promise>
+ ConsumeBody(JSContext* aCx, FetchConsumeType aType, ErrorResult& aRv);
+
+ void
+ LockStream(JSContext* aCx, JS::HandleObject aStream, ErrorResult& aRv);
+
+ bool
+ IsOnTargetThread()
+ {
+ return NS_IsMainThread() == !mWorkerPrivate;
+ }
+
+ void
+ AssertIsOnTargetThread()
+ {
+ MOZ_ASSERT(IsOnTargetThread());
+ }
+
+ // Only ever set once, always on target thread.
+ bool mBodyUsed;
+ nsCString mMimeType;
+
+ // The main-thread event target for runnable dispatching.
+ nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Fetch_h
diff --git a/dom/fetch/FetchStream.cpp b/dom/fetch/FetchStream.cpp
new file mode 100644
index 0000000000..1c22a71405
--- /dev/null
+++ b/dom/fetch/FetchStream.cpp
@@ -0,0 +1,640 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchStream.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Maybe.h"
+#include "nsNetCID.h"
+#include "nsITransport.h"
+#include "nsIStreamTransportService.h"
+#include "nsProxyRelease.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "Workers.h"
+
+#include "mozilla/dom/DOMError.h"
+
+#define FETCH_STREAM_FLAG 0
+
+static NS_DEFINE_CID(kStreamTransportServiceCID,
+ NS_STREAMTRANSPORTSERVICE_CID);
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+namespace {
+
+class FetchStreamWorkerHolder final : public WorkerHolder
+{
+public:
+ explicit FetchStreamWorkerHolder(FetchStream* aStream)
+ : WorkerHolder()
+ , mStream(aStream)
+ , mWasNotified(false)
+ {}
+
+ bool Notify(Status aStatus) override
+ {
+ if (!mWasNotified) {
+ mWasNotified = true;
+ mStream->Close();
+ }
+
+ return true;
+ }
+
+ WorkerPrivate* GetWorkerPrivate() const
+ {
+ return mWorkerPrivate;
+ }
+
+private:
+ RefPtr<FetchStream> mStream;
+ bool mWasNotified;
+};
+
+class FetchStreamWorkerHolderShutdown final : public WorkerControlRunnable
+{
+public:
+ FetchStreamWorkerHolderShutdown(WorkerPrivate* aWorkerPrivate,
+ UniquePtr<WorkerHolder>&& aHolder,
+ nsCOMPtr<nsIGlobalObject>&& aGlobal,
+ RefPtr<FetchStreamHolder>&& aStreamHolder)
+ : WorkerControlRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount)
+ , mHolder(Move(aHolder))
+ , mGlobal(Move(aGlobal))
+ , mStreamHolder(Move(aStreamHolder))
+ {}
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ mHolder = nullptr;
+ mGlobal = nullptr;
+
+ mStreamHolder->NullifyStream();
+ mStreamHolder = nullptr;
+
+ return true;
+ }
+
+ // This runnable starts from a JS Thread. We need to disable a couple of
+ // assertions by overriding the following methods.
+
+ bool
+ PreDispatch(WorkerPrivate* aWorkerPrivate) override
+ {
+ return true;
+ }
+
+ void
+ PostDispatch(WorkerPrivate* aWorkerPrivate, bool aDispatchResult) override
+ {}
+
+private:
+ UniquePtr<WorkerHolder> mHolder;
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<FetchStreamHolder> mStreamHolder;
+};
+
+} // anonymous
+
+NS_IMPL_ISUPPORTS(FetchStream, nsIInputStreamCallback, nsIObserver,
+ nsISupportsWeakReference)
+
+/* static */ void
+FetchStream::Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
+ nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
+ JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aCx);
+ MOZ_DIAGNOSTIC_ASSERT(aInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
+
+ RefPtr<FetchStream> stream = new FetchStream(aGlobal, aStreamHolder, aInputStream);
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (NS_WARN_IF(!os)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ aRv = os->AddObserver(stream, DOM_WINDOW_DESTROYED_TOPIC, true);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ } else {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ UniquePtr<FetchStreamWorkerHolder> holder(
+ new FetchStreamWorkerHolder(stream));
+ if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ // Note, this will create a ref-cycle between the holder and the stream.
+ // The cycle is broken when the stream is closed or the worker begins
+ // shutting down.
+ stream->mWorkerHolder = Move(holder);
+ }
+
+ if (!JS::HasReadableStreamCallbacks(aCx)) {
+ JS::SetReadableStreamCallbacks(aCx,
+ &FetchStream::RequestDataCallback,
+ &FetchStream::WriteIntoReadRequestCallback,
+ &FetchStream::CancelCallback,
+ &FetchStream::ClosedCallback,
+ &FetchStream::ErroredCallback,
+ &FetchStream::FinalizeCallback);
+ }
+
+ JS::Rooted<JSObject*> body(aCx,
+ JS::NewReadableExternalSourceStreamObject(aCx, stream, FETCH_STREAM_FLAG));
+ if (!body) {
+ aRv.StealExceptionFromJSContext(aCx);
+ return;
+ }
+
+ // This will be released in FetchStream::FinalizeCallback(). We are
+ // guaranteed the jsapi will call FinalizeCallback when ReadableStream
+ // js object is finalized.
+ NS_ADDREF(stream.get());
+
+ aStream.set(body);
+}
+
+/* static */ void
+FetchStream::RequestDataCallback(JSContext* aCx,
+ JS::HandleObject aStream,
+ void* aUnderlyingSource,
+ uint8_t aFlags,
+ size_t aDesiredSize)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+ MOZ_DIAGNOSTIC_ASSERT(JS::ReadableStreamIsDisturbed(aStream));
+
+ RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
+ stream->AssertIsOnOwningThread();
+
+ MutexAutoLock lock(stream->mMutex);
+
+ MOZ_DIAGNOSTIC_ASSERT(stream->mState == eInitializing ||
+ stream->mState == eWaiting ||
+ stream->mState == eChecking ||
+ stream->mState == eReading);
+
+ if (stream->mState == eReading) {
+ // We are already reading data.
+ return;
+ }
+
+ if (stream->mState == eChecking) {
+ // If we are looking for more data, there is nothing else we should do:
+ // let's move this checking operation in a reading.
+ MOZ_ASSERT(stream->mInputStream);
+ stream->mState = eReading;
+ return;
+ }
+
+ if (stream->mState == eInitializing) {
+ // The stream has been used for the first time.
+ stream->mStreamHolder->MarkAsRead();
+ }
+
+ stream->mState = eReading;
+
+ if (!stream->mInputStream) {
+ // This is the first use of the stream. Let's convert the
+ // mOriginalInputStream into an nsIAsyncInputStream.
+ MOZ_ASSERT(stream->mOriginalInputStream);
+
+ bool nonBlocking = false;
+ nsresult rv = stream->mOriginalInputStream->IsNonBlocking(&nonBlocking);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ nsCOMPtr<nsIAsyncInputStream> asyncStream =
+ do_QueryInterface(stream->mOriginalInputStream);
+ if (!nonBlocking || !asyncStream) {
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(kStreamTransportServiceCID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ nsCOMPtr<nsITransport> transport;
+ rv = sts->CreateInputTransport(stream->mOriginalInputStream,
+ /* aStartOffset */ 0,
+ /* aReadLimit */ -1,
+ /* aCloseWhenDone */ true,
+ getter_AddRefs(transport));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ nsCOMPtr<nsIInputStream> wrapper;
+ rv = transport->OpenInputStream(/* aFlags */ 0,
+ /* aSegmentSize */ 0,
+ /* aSegmentCount */ 0,
+ getter_AddRefs(wrapper));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ asyncStream = do_QueryInterface(wrapper);
+ }
+
+ stream->mInputStream = asyncStream;
+ stream->mOriginalInputStream = nullptr;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(!stream->mOriginalInputStream);
+
+ nsresult rv =
+ stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ // All good.
+}
+
+/* static */ void
+FetchStream::WriteIntoReadRequestCallback(JSContext* aCx,
+ JS::HandleObject aStream,
+ void* aUnderlyingSource,
+ uint8_t aFlags, void* aBuffer,
+ size_t aLength, size_t* aByteWritten)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+ MOZ_DIAGNOSTIC_ASSERT(aBuffer);
+ MOZ_DIAGNOSTIC_ASSERT(aByteWritten);
+
+ RefPtr<FetchStream> stream = static_cast<FetchStream*>(aUnderlyingSource);
+ stream->AssertIsOnOwningThread();
+
+ MutexAutoLock lock(stream->mMutex);
+
+ MOZ_DIAGNOSTIC_ASSERT(stream->mInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(stream->mState == eWriting);
+ stream->mState = eChecking;
+
+ uint32_t written;
+ nsresult rv =
+ stream->mInputStream->Read(static_cast<char*>(aBuffer), aLength, &written);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ *aByteWritten = written;
+
+ if (written == 0) {
+ stream->CloseAndReleaseObjects(aCx, lock, aStream);
+ return;
+ }
+
+ rv = stream->mInputStream->AsyncWait(stream, 0, 0, stream->mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ stream->ErrorPropagation(aCx, lock, aStream, rv);
+ return;
+ }
+
+ // All good.
+}
+
+/* static */ JS::Value
+FetchStream::CancelCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ JS::HandleValue aReason)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+
+ // This is safe because we created an extra reference in FetchStream::Create()
+ // that won't be released until FetchStream::FinalizeCallback() is called.
+ // We are guaranteed that won't happen until the js ReadableStream object
+ // is finalized.
+ FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
+ stream->AssertIsOnOwningThread();
+
+ if (stream->mInputStream) {
+ stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+ }
+
+ stream->ReleaseObjects();
+
+ return JS::UndefinedValue();
+}
+
+/* static */ void
+FetchStream::ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+}
+
+/* static */ void
+FetchStream::ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ JS::HandleValue aReason)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+
+ // This is safe because we created an extra reference in FetchStream::Create()
+ // that won't be released until FetchStream::FinalizeCallback() is called.
+ // We are guaranteed that won't happen until the js ReadableStream object
+ // is finalized.
+ FetchStream* stream = static_cast<FetchStream*>(aUnderlyingSource);
+ stream->AssertIsOnOwningThread();
+
+ if (stream->mState == eInitializing) {
+ // The stream has been used for the first time.
+ stream->mStreamHolder->MarkAsRead();
+ }
+
+ if (stream->mInputStream) {
+ stream->mInputStream->CloseWithStatus(NS_BASE_STREAM_CLOSED);
+ }
+
+ stream->ReleaseObjects();
+}
+
+void
+FetchStream::FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags)
+{
+ MOZ_DIAGNOSTIC_ASSERT(aUnderlyingSource);
+ MOZ_DIAGNOSTIC_ASSERT(aFlags == FETCH_STREAM_FLAG);
+
+ // This can be called in any thread.
+
+ // This takes ownership of the ref created in FetchStream::Create().
+ RefPtr<FetchStream> stream =
+ dont_AddRef(static_cast<FetchStream*>(aUnderlyingSource));
+
+ stream->ReleaseObjects();
+}
+
+FetchStream::FetchStream(nsIGlobalObject* aGlobal,
+ FetchStreamHolder* aStreamHolder,
+ nsIInputStream* aInputStream)
+ : mMutex("FetchStream::mMutex")
+ , mState(eInitializing)
+ , mGlobal(aGlobal)
+ , mStreamHolder(aStreamHolder)
+ , mOriginalInputStream(aInputStream)
+ // TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
+ // When we have the Dispatcher API in the tree, see Issue #1442
+ , mOwningEventTarget(NS_GetCurrentThread())
+{
+ MOZ_DIAGNOSTIC_ASSERT(aInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(aStreamHolder);
+}
+
+FetchStream::~FetchStream()
+{
+}
+
+void
+FetchStream::ErrorPropagation(JSContext* aCx,
+ const MutexAutoLock& aProofOfLock,
+ JS::HandleObject aStream,
+ nsresult aError)
+{
+ AssertIsOnOwningThread();
+
+ // Nothing to do.
+ if (mState == eClosed) {
+ return;
+ }
+
+ // Let's close the stream.
+ if (aError == NS_BASE_STREAM_CLOSED) {
+ CloseAndReleaseObjects(aCx, aProofOfLock, aStream);
+ return;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+
+ // Let's use a generic error.
+ RefPtr<DOMError> error = new DOMError(window, NS_ERROR_DOM_TYPE_ERR);
+
+ JS::Rooted<JS::Value> errorValue(aCx);
+ if (ToJSValue(aCx, error, &errorValue)) {
+ MutexAutoUnlock unlock(mMutex);
+ JS::ReadableStreamError(aCx, aStream, errorValue);
+ }
+
+ ReleaseObjects(aProofOfLock);
+}
+
+NS_IMETHODIMP
+FetchStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+ AssertIsOnOwningThread();
+ MOZ_DIAGNOSTIC_ASSERT(aStream);
+
+ Maybe<MutexAutoLock> lock;
+ lock.emplace(mMutex);
+
+ // Already closed. We have nothing else to do here.
+ if (mState == eClosed) {
+ return NS_OK;
+ }
+
+ nsAutoMicroTask mt;
+ AutoEntryScript aes(mGlobal, "fetch body data available");
+
+ MOZ_DIAGNOSTIC_ASSERT(mInputStream);
+ MOZ_DIAGNOSTIC_ASSERT(mState == eReading || mState == eChecking);
+
+ JSContext* cx = aes.cx();
+ JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
+
+ uint64_t size = 0;
+ nsresult rv = mInputStream->Available(&size);
+ if (NS_SUCCEEDED(rv) && size == 0) {
+ // In theory this should not happen. If size is 0, the stream should be
+ // considered closed.
+ rv = NS_BASE_STREAM_CLOSED;
+ }
+
+ // No warning for stream closed.
+ if (rv == NS_BASE_STREAM_CLOSED || NS_WARN_IF(NS_FAILED(rv))) {
+ ErrorPropagation(cx, *lock, stream, rv);
+ return NS_OK;
+ }
+
+ // This extra checking is completed. Let's wait for the next read request.
+ if (mState == eChecking) {
+ mState = eWaiting;
+ return NS_OK;
+ }
+
+ mState = eWriting;
+
+ lock.reset();
+
+ JS::ReadableStreamUpdateDataAvailableFromSource(cx, stream, size);
+
+ return NS_OK;
+}
+
+/* static */ nsresult
+FetchStream::RetrieveInputStream(void* aUnderlyingReadableStreamSource,
+ nsIInputStream** aInputStream)
+{
+ MOZ_ASSERT(aUnderlyingReadableStreamSource);
+ MOZ_ASSERT(aInputStream);
+
+ RefPtr<FetchStream> stream =
+ static_cast<FetchStream*>(aUnderlyingReadableStreamSource);
+ stream->AssertIsOnOwningThread();
+
+ // if mOriginalInputStream is null, the reading already started. We don't want
+ // to expose the internal inputStream.
+ if (NS_WARN_IF(!stream->mOriginalInputStream)) {
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream = stream->mOriginalInputStream;
+ inputStream.forget(aInputStream);
+ return NS_OK;
+}
+
+void
+FetchStream::Close()
+{
+ AssertIsOnOwningThread();
+
+ MutexAutoLock lock(mMutex);
+
+ if (mState == eClosed) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
+ ReleaseObjects(lock);
+ return;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> stream(cx, mStreamHolder->ReadableStreamBody());
+
+ CloseAndReleaseObjects(cx, lock, stream);
+}
+
+void
+FetchStream::CloseAndReleaseObjects(JSContext* aCx,
+ const MutexAutoLock& aProofOfLock,
+ JS::HandleObject aStream)
+{
+ AssertIsOnOwningThread();
+ MOZ_DIAGNOSTIC_ASSERT(mState != eClosed);
+
+ ReleaseObjects(aProofOfLock);
+
+ MutexAutoUnlock unlock(mMutex);
+
+ if (JS::ReadableStreamIsReadable(aStream)) {
+ JS::ReadableStreamClose(aCx, aStream);
+ }
+}
+
+void
+FetchStream::ReleaseObjects()
+{
+ MutexAutoLock lock(mMutex);
+ ReleaseObjects(lock);
+}
+
+void
+FetchStream::ReleaseObjects(const MutexAutoLock& aProofOfLock)
+{
+ // This method can be called on 2 possible threads: the owning one and a JS
+ // thread used to release resources. If we are on the JS thread, we need to
+ // dispatch a runnable to go back to the owning thread in order to release
+ // resources correctly.
+
+ if (mState == eClosed) {
+ // Already gone. Nothing to do.
+ return;
+ }
+
+ mState = eClosed;
+
+ if (mWorkerHolder) {
+ RefPtr<FetchStreamWorkerHolderShutdown> r =
+ new FetchStreamWorkerHolderShutdown(
+ static_cast<FetchStreamWorkerHolder*>(mWorkerHolder.get())->GetWorkerPrivate(),
+ Move(mWorkerHolder), Move(mGlobal), Move(mStreamHolder));
+ r->Dispatch();
+ } else {
+ RefPtr<FetchStream> self = this;
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ [self] () {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(self, DOM_WINDOW_DESTROYED_TOPIC);
+ }
+ self->mGlobal = nullptr;
+
+ self->mStreamHolder->NullifyStream();
+ self->mStreamHolder = nullptr;
+ });
+
+ NS_DispatchToMainThread(r);
+ }
+}
+
+#ifdef DEBUG
+void
+FetchStream::AssertIsOnOwningThread()
+{
+ NS_ASSERT_OWNINGTHREAD(FetchStream);
+}
+#endif
+
+// nsIObserver
+// -----------
+
+NS_IMETHODIMP
+FetchStream::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData)
+{
+ AssertIsOnMainThread();
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0);
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (SameCOMIdentity(aSubject, window)) {
+ Close();
+ }
+
+ return NS_OK;
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/fetch/FetchStream.h b/dom/fetch/FetchStream.h
new file mode 100644
index 0000000000..66e1de5642
--- /dev/null
+++ b/dom/fetch/FetchStream.h
@@ -0,0 +1,158 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FetchStream_h
+#define mozilla_dom_FetchStream_h
+
+#include "Fetch.h"
+#include "jsapi.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIObserver.h"
+#include "nsISupportsImpl.h"
+#include "nsWeakReference.h"
+
+class nsIGlobalObject;
+
+class nsIInputStream;
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+class WorkerHolder;
+}
+
+class FetchStreamHolder;
+
+class FetchStream final : public nsIInputStreamCallback
+ , public nsIObserver
+ , public nsSupportsWeakReference
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIOBSERVER
+
+ static void
+ Create(JSContext* aCx, FetchStreamHolder* aStreamHolder,
+ nsIGlobalObject* aGlobal, nsIInputStream* aInputStream,
+ JS::MutableHandle<JSObject*> aStream, ErrorResult& aRv);
+
+ void
+ Close();
+
+ static nsresult
+ RetrieveInputStream(void* aUnderlyingReadableStreamSource,
+ nsIInputStream** aInputStream);
+private:
+ FetchStream(nsIGlobalObject* aGlobal,
+ FetchStreamHolder* aStreamHolder,
+ nsIInputStream* aInputStream);
+ ~FetchStream();
+
+#ifdef DEBUG
+ void
+ AssertIsOnOwningThread();
+#else
+ void
+ AssertIsOnOwningThread() {}
+#endif
+
+ static void
+ RequestDataCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ size_t aDesiredSize);
+
+ static void
+ WriteIntoReadRequestCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ void* aBuffer, size_t aLength,
+ size_t* aByteWritten);
+
+ static JS::Value
+ CancelCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ JS::HandleValue aReason);
+
+ static void
+ ClosedCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags);
+
+ static void
+ ErroredCallback(JSContext* aCx, JS::HandleObject aStream,
+ void* aUnderlyingSource, uint8_t aFlags,
+ JS::HandleValue reason);
+
+ static void
+ FinalizeCallback(void* aUnderlyingSource, uint8_t aFlags);
+
+ void
+ ErrorPropagation(JSContext* aCx,
+ const MutexAutoLock& aProofOfLock,
+ JS::HandleObject aStream, nsresult aRv);
+
+ void
+ CloseAndReleaseObjects(JSContext* aCx,
+ const MutexAutoLock& aProofOfLock,
+ JS::HandleObject aSteam);
+
+ class WorkerShutdown;
+
+ void
+ ReleaseObjects(const MutexAutoLock& aProofOfLock);
+
+ void
+ ReleaseObjects();
+
+ // Common methods
+
+ enum State {
+ // This is the beginning state before any reading operation.
+ eInitializing,
+
+ // RequestDataCallback has not been called yet. We haven't started to read
+ // data from the stream yet.
+ eWaiting,
+
+ // We are reading data in a separate I/O thread.
+ eReading,
+
+ // We are ready to write something in the JS Buffer.
+ eWriting,
+
+ // After a writing, we want to check if the stream is closed. After the
+ // check, we go back to eWaiting. If a reading request happens in the
+ // meantime, we move to eReading state.
+ eChecking,
+
+ // Operation completed.
+ eClosed,
+ };
+
+ // We need a mutex because JS engine can release FetchStream on a non-owning
+ // thread. We must be sure that the releasing of resources doesn't trigger
+ // race conditions.
+ Mutex mMutex;
+
+ // Protected by mutex.
+ State mState;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<FetchStreamHolder> mStreamHolder;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ // This is the original inputStream received during the CTOR. It will be
+ // converted into an nsIAsyncInputStream and stored into mInputStream at the
+ // first use.
+ nsCOMPtr<nsIInputStream> mOriginalInputStream;
+ nsCOMPtr<nsIAsyncInputStream> mInputStream;
+
+ UniquePtr<workers::WorkerHolder> mWorkerHolder;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FetchStream_h
diff --git a/dom/fetch/FetchStreamReader.cpp b/dom/fetch/FetchStreamReader.cpp
new file mode 100644
index 0000000000..c6f5ca4d1d
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FetchStreamReader.h"
+#include "InternalResponse.h"
+#include "mozilla/dom/PromiseBinding.h"
+#include "mozilla/dom/DOMException.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+namespace {
+
+class FetchStreamReaderWorkerHolder final : public WorkerHolder
+{
+public:
+ explicit FetchStreamReaderWorkerHolder(FetchStreamReader* aReader)
+ : WorkerHolder(WorkerHolder::Behavior::AllowIdleShutdownStart)
+ , mReader(aReader)
+ , mWasNotified(false)
+ {}
+
+ bool Notify(Status aStatus) override
+ {
+ if (!mWasNotified) {
+ mWasNotified = true;
+ // The WorkerPrivate does have a context available, and we could pass it
+ // here to trigger cancellation of the reader, but the author of this
+ // comment chickened out.
+ mReader->CloseAndRelease(nullptr, NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+
+ return true;
+ }
+
+private:
+ RefPtr<FetchStreamReader> mReader;
+ bool mWasNotified;
+};
+
+} // anonymous
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(FetchStreamReader)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(FetchStreamReader)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(FetchStreamReader)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(FetchStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReader)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FetchStreamReader)
+ NS_INTERFACE_MAP_ENTRY(nsIOutputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStreamCallback)
+NS_INTERFACE_MAP_END
+
+/* static */ nsresult
+FetchStreamReader::Create(JSContext* aCx, nsIGlobalObject* aGlobal,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream)
+{
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aGlobal);
+ MOZ_ASSERT(aStreamReader);
+ MOZ_ASSERT(aInputStream);
+
+ RefPtr<FetchStreamReader> streamReader = new FetchStreamReader(aGlobal);
+
+ nsCOMPtr<nsIAsyncInputStream> pipeIn;
+
+ nsresult rv = NS_NewPipe2(getter_AddRefs(pipeIn),
+ getter_AddRefs(streamReader->mPipeOut),
+ true, true, 0, 0);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!NS_IsMainThread()) {
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ MOZ_ASSERT(workerPrivate);
+
+ // We need to know when the worker goes away.
+ UniquePtr<FetchStreamReaderWorkerHolder> holder(
+ new FetchStreamReaderWorkerHolder(streamReader));
+ if (NS_WARN_IF(!holder->HoldWorker(workerPrivate, Closing))) {
+ streamReader->mPipeOut->CloseWithStatus(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_DOM_INVALID_STATE_ERR;
+ }
+
+ // These 2 objects create a ref-cycle here that is broken when the stream is
+ // closed or the worker shutsdown.
+ streamReader->mWorkerHolder = Move(holder);
+ }
+
+ pipeIn.forget(aInputStream);
+ streamReader.forget(aStreamReader);
+ return NS_OK;
+}
+
+FetchStreamReader::FetchStreamReader(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal)
+ // TODO: Replace with mGlobal->EventTargetFor(TaskCategory::Other)
+ // When we have the Dispatcher API in the tree, see Issue #1442
+ , mOwningEventTarget(NS_GetCurrentThread())
+ , mBufferRemaining(0)
+ , mBufferOffset(0)
+ , mStreamClosed(false)
+{
+ MOZ_ASSERT(aGlobal);
+}
+
+FetchStreamReader::~FetchStreamReader()
+{
+ CloseAndRelease(nullptr, NS_BASE_STREAM_CLOSED);
+}
+
+// If a context is provided, an attempt will be made to cancel the reader. The
+// only situation where we don't expect to have a context is when closure is
+// being triggered from the destructor or the WorkerHolder is notifying. If
+// we're at the destructor, it's far too late to cancel anything. And if the
+// WorkerHolder is being notified, the global is going away, so there's also
+// no need to do further JS work.
+void
+FetchStreamReader::CloseAndRelease(JSContext* aCx, nsresult aStatus)
+{
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+
+ if (mStreamClosed) {
+ // Already closed.
+ return;
+ }
+
+ RefPtr<FetchStreamReader> kungFuDeathGrip = this;
+
+ if (aCx) {
+ MOZ_ASSERT(mReader);
+
+ RefPtr<DOMException> error = DOMException::Create(aStatus);
+
+ JS::Rooted<JS::Value> errorValue(aCx);
+ if (ToJSValue(aCx, error, &errorValue)) {
+ JS::Rooted<JSObject*> reader(aCx, mReader);
+ // It's currently safe to cancel an already closed reader because, per the
+ // comments in ReadableStream::cancel() conveying the spec, step 2 of
+ // 3.4.3 that specified ReadableStreamCancel is: If stream.[[state]] is
+ // "closed", return a new promise resolved with undefined.
+ JS::ReadableStreamReaderCancel(aCx, reader, errorValue);
+ }
+ }
+ mStreamClosed = true;
+
+ mGlobal = nullptr;
+
+ mPipeOut->CloseWithStatus(aStatus);
+ mPipeOut = nullptr;
+
+ mWorkerHolder = nullptr;
+
+ mReader = nullptr;
+ mBuffer = nullptr;
+}
+
+void
+FetchStreamReader::StartConsuming(JSContext* aCx,
+ JS::HandleObject aStream,
+ JS::MutableHandle<JSObject*> aReader,
+ ErrorResult& aRv)
+{
+ MOZ_DIAGNOSTIC_ASSERT(!mReader);
+ MOZ_DIAGNOSTIC_ASSERT(aStream);
+
+ JS::Rooted<JSObject*> reader(aCx,
+ JS::ReadableStreamGetReader(aCx, aStream,
+ JS::ReadableStreamReaderMode::Default));
+ if (!reader) {
+ aRv.StealExceptionFromJSContext(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ mReader = reader;
+ aReader.set(reader);
+
+ aRv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+}
+
+// nsIOutputStreamCallback interface
+
+NS_IMETHODIMP
+FetchStreamReader::OnOutputStreamReady(nsIAsyncOutputStream* aStream)
+{
+ NS_ASSERT_OWNINGTHREAD(FetchStreamReader);
+ MOZ_ASSERT(aStream == mPipeOut);
+ MOZ_ASSERT(mReader);
+
+ if (mStreamClosed) {
+ return NS_OK;
+ }
+
+ if (mBuffer) {
+ return WriteBuffer();
+ }
+
+ // TODO: We need to verify this is the correct global per the spec.
+ // See bug 1385890.
+ AutoEntryScript aes(mGlobal, "ReadableStreamReader.read", !mWorkerHolder);
+
+ JS::Rooted<JSObject*> reader(aes.cx(), mReader);
+ JS::Rooted<JSObject*> promise(aes.cx(),
+ JS::ReadableStreamDefaultReaderRead(aes.cx(),
+ reader));
+ if (NS_WARN_IF(!promise)) {
+ // Let's close the stream.
+ CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<Promise> domPromise = Promise::CreateFromExisting(mGlobal, promise);
+ if (NS_WARN_IF(!domPromise)) {
+ // Let's close the stream.
+ CloseAndRelease(aes.cx(), NS_ERROR_DOM_INVALID_STATE_ERR);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Let's wait.
+ domPromise->AppendNativeHandler(this);
+ return NS_OK;
+}
+
+void
+FetchStreamReader::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ if (mStreamClosed) {
+ return;
+ }
+
+ // This promise should be resolved with { done: boolean, value: something },
+ // "value" is interesting only if done is false.
+
+ // We don't want to play with JS api, let's WebIDL bindings doing it for us.
+ // FetchReadableStreamReadDataDone is a dictionary with just a boolean, if the
+ // parsing succeeded, we can proceed with the parsing of the "value", which it
+ // must be a Uint8Array.
+ FetchReadableStreamReadDataDone valueDone;
+ if (!valueDone.Init(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (valueDone.mDone) {
+ // Stream is completed.
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ UniquePtr<FetchReadableStreamReadDataArray> value(
+ new FetchReadableStreamReadDataArray);
+ if (!value->Init(aCx, aValue) || !value->mValue.WasPassed()) {
+ JS_ClearPendingException(aCx);
+ CloseAndRelease(aCx, NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ Uint8Array& array = value->mValue.Value();
+ array.ComputeLengthAndData();
+ uint32_t len = array.Length();
+
+ if (len == 0) {
+ // If there is nothing to read, let's do another reading.
+ OnOutputStreamReady(mPipeOut);
+ return;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mBuffer);
+ mBuffer = Move(value);
+
+ mBufferOffset = 0;
+ mBufferRemaining = len;
+
+ nsresult rv = WriteBuffer();
+ if (NS_FAILED(rv)) {
+ // DOMException only understands errors from domerr.msg, so we normalize to
+ // identifying an abort if the write fails.
+ CloseAndRelease(aCx, NS_ERROR_DOM_ABORT_ERR);
+ }
+}
+
+nsresult
+FetchStreamReader::WriteBuffer()
+{
+ MOZ_ASSERT(mBuffer);
+ MOZ_ASSERT(mBuffer->mValue.WasPassed());
+
+ Uint8Array& array = mBuffer->mValue.Value();
+ char* data = reinterpret_cast<char*>(array.Data());
+
+ while (1) {
+ uint32_t written = 0;
+ nsresult rv =
+ mPipeOut->Write(data + mBufferOffset, mBufferRemaining, &written);
+
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ break;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ MOZ_ASSERT(written <= mBufferRemaining);
+ mBufferRemaining -= written;
+ mBufferOffset += written;
+
+ if (mBufferRemaining == 0) {
+ mBuffer = nullptr;
+ break;
+ }
+ }
+
+ nsresult rv = mPipeOut->AsyncWait(this, 0, 0, mOwningEventTarget);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void
+FetchStreamReader::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ ReportErrorToConsole(aCx, aValue);
+ CloseAndRelease(aCx, NS_ERROR_FAILURE);
+}
+
+void
+FetchStreamReader::ReportErrorToConsole(JSContext* aCx,
+ JS::Handle<JS::Value> aValue)
+{
+ nsCString sourceSpec;
+ uint32_t line = 0;
+ uint32_t column = 0;
+ nsString valueString;
+
+ nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line,
+ &column, valueString);
+
+ nsTArray<nsString> params;
+ params.AppendElement(valueString);
+
+ RefPtr<ConsoleReportCollector> reporter = new ConsoleReportCollector();
+ reporter->AddConsoleReport(nsIScriptError::errorFlag,
+ NS_LITERAL_CSTRING("ReadableStreamReader.read"),
+ nsContentUtils::eDOM_PROPERTIES,
+ sourceSpec, line, column,
+ NS_LITERAL_CSTRING("ReadableStreamReadingFailed"),
+ params);
+
+ uint64_t innerWindowId = 0;
+
+ if (NS_IsMainThread()) {
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (window) {
+ innerWindowId = window->WindowID();
+ }
+ reporter->FlushReportsByWindowId(innerWindowId);
+ return;
+ }
+
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx);
+ if (workerPrivate) {
+ innerWindowId = workerPrivate->WindowID();
+ }
+
+ RefPtr<Runnable> r = NS_NewRunnableFunction(
+ [reporter, innerWindowId] () {
+ reporter->FlushReportsByWindowId(innerWindowId);
+ });
+
+ workerPrivate->DispatchToMainThread(r.forget());
+}
+
+} // dom namespace
+} // mozilla namespace
diff --git a/dom/fetch/FetchStreamReader.h b/dom/fetch/FetchStreamReader.h
new file mode 100644
index 0000000000..c25685046c
--- /dev/null
+++ b/dom/fetch/FetchStreamReader.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FetchStreamReader_h
+#define mozilla_dom_FetchStreamReader_h
+
+#include "jsapi.h"
+#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsIAsyncOutputStream.h"
+
+namespace mozilla {
+namespace dom {
+
+namespace workers {
+class WorkerHolder;
+}
+
+class FetchStreamReader final : public nsIOutputStreamCallback
+ , public PromiseNativeHandler
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(FetchStreamReader, nsIOutputStreamCallback)
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+
+ // This creates a nsIInputStream able to retrieve data from the ReadableStream
+ // object. The reading starts when StartConsuming() is called.
+ static nsresult
+ Create(JSContext* aCx, nsIGlobalObject* aGlobal,
+ FetchStreamReader** aStreamReader,
+ nsIInputStream** aInputStream);
+
+ void
+ ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ void
+ RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+ // Idempotently close the output stream and null out all state. If aCx is
+ // provided, the reader will also be canceled. aStatus must be a DOM error
+ // as understood by DOMException because it will be provided as the
+ // cancellation reason.
+ void
+ CloseAndRelease(JSContext* aCx, nsresult aStatus);
+
+ void
+ StartConsuming(JSContext* aCx,
+ JS::HandleObject aStream,
+ JS::MutableHandle<JSObject*> aReader,
+ ErrorResult& aRv);
+
+private:
+ explicit FetchStreamReader(nsIGlobalObject* aGlobal);
+ ~FetchStreamReader();
+
+ nsresult
+ WriteBuffer();
+
+ void
+ ReportErrorToConsole(JSContext* aCx, JS::Handle<JS::Value> aValue);
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ nsCOMPtr<nsIEventTarget> mOwningEventTarget;
+
+ nsCOMPtr<nsIAsyncOutputStream> mPipeOut;
+
+ UniquePtr<workers::WorkerHolder> mWorkerHolder;
+
+ JS::Heap<JSObject*> mReader;
+
+ UniquePtr<FetchReadableStreamReadDataArray> mBuffer;
+ uint32_t mBufferRemaining;
+ uint32_t mBufferOffset;
+
+ bool mStreamClosed;
+};
+
+} // dom namespace
+} // mozilla namespace
+
+#endif // mozilla_dom_FetchStreamReader_h
diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp
index ec32c4e216..f4fd1912f9 100644
--- a/dom/fetch/InternalResponse.cpp
+++ b/dom/fetch/InternalResponse.cpp
@@ -139,18 +139,18 @@ InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse,
}
already_AddRefed<InternalResponse>
-InternalResponse::Clone()
+InternalResponse::Clone(CloneType aCloneType)
{
RefPtr<InternalResponse> clone = CreateIncompleteCopy();
clone->mHeaders = new InternalHeaders(*mHeaders);
if (mWrappedResponse) {
- clone->mWrappedResponse = mWrappedResponse->Clone();
+ clone->mWrappedResponse = mWrappedResponse->Clone(aCloneType);
MOZ_ASSERT(!mBody);
return clone.forget();
}
- if (!mBody) {
+ if (!mBody || aCloneType == eDontCloneInputStream) {
return clone.forget();
}
diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h
index e4b4a0ab62..d5c759d577 100644
--- a/dom/fetch/InternalResponse.h
+++ b/dom/fetch/InternalResponse.h
@@ -46,7 +46,13 @@ public:
M* aManager,
UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream);
- already_AddRefed<InternalResponse> Clone();
+ enum CloneType
+ {
+ eCloneInputStream,
+ eDontCloneInputStream,
+ };
+
+ already_AddRefed<InternalResponse> Clone(CloneType eCloneType);
static already_AddRefed<InternalResponse>
NetworkError()
diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp
index ab87a3215a..b79b539772 100644
--- a/dom/fetch/Request.cpp
+++ b/dom/fetch/Request.cpp
@@ -29,7 +29,25 @@ using namespace workers;
NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Request)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Request)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Request)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Request)
+ MOZ_DIAGNOSTIC_ASSERT(!tmp->mReadableStreamReader);
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -37,8 +55,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
NS_INTERFACE_MAP_END
Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest, AbortSignal* aSignal)
- : FetchBody<Request>()
- , mOwner(aOwner)
+ : FetchBody<Request>(aOwner)
, mRequest(aRequest)
, mSignal(aSignal)
{
@@ -557,17 +574,15 @@ Request::Constructor(const GlobalObject& aGlobal,
}
if (aInit.mBody.WasPassed()) {
- const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable =
- aInit.mBody.Value();
+ const Nullable<fetch::OwningBodyInit>& bodyInitNullable = aInit.mBody.Value();
if (!bodyInitNullable.IsNull()) {
- const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit =
- bodyInitNullable.Value();
+ const fetch::OwningBodyInit& bodyInit = bodyInitNullable.Value();
nsCOMPtr<nsIInputStream> stream;
- nsAutoCString contentType;
+ nsAutoCString contentTypeWithCharset;
uint64_t contentLengthUnused;
aRv = ExtractByteStreamFromBody(bodyInit,
getter_AddRefs(stream),
- contentType,
+ contentTypeWithCharset,
contentLengthUnused);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@@ -575,10 +590,10 @@ Request::Constructor(const GlobalObject& aGlobal,
temporaryBody = stream;
- if (!contentType.IsVoid() &&
+ if (!contentTypeWithCharset.IsVoid() &&
!requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"),
- contentType, aRv);
+ contentTypeWithCharset, aRv);
}
if (NS_WARN_IF(aRv.Failed())) {
@@ -599,7 +614,10 @@ Request::Constructor(const GlobalObject& aGlobal,
inputReq->GetBody(getter_AddRefs(body));
if (body) {
inputReq->SetBody(nullptr);
- inputReq->SetBodyUsed();
+ inputReq->SetBodyUsed(aGlobal.Context(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
}
}
return domRequest.forget();
diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h
index 8c01cbd799..d5fc09fe25 100644
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -128,6 +128,8 @@ public:
void
GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); }
+ using FetchBody::GetBody;
+
void
SetBody(nsIInputStream* aStream) { return mRequest->SetBody(aStream); }
@@ -162,7 +164,6 @@ public:
private:
~Request();
- nsCOMPtr<nsIGlobalObject> mOwner;
RefPtr<InternalRequest> mRequest;
// Lazily created.
diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp
index 42b25ae1d9..3ee5e93d4a 100644
--- a/dom/fetch/Response.cpp
+++ b/dom/fetch/Response.cpp
@@ -12,12 +12,16 @@
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/FetchBinding.h"
+#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/Headers.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/URL.h"
#include "nsDOMString.h"
+#include "BodyExtractor.h"
+#include "FetchStream.h"
+#include "FetchStreamReader.h"
#include "InternalResponse.h"
#include "WorkerPrivate.h"
@@ -26,7 +30,31 @@ namespace dom {
NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner, mHeaders)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(Response)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Response)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mFetchStreamReader)
+
+ tmp->mReadableStreamBody = nullptr;
+ tmp->mReadableStreamReader = nullptr;
+
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Response)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHeaders)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFetchStreamReader)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Response)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamBody)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mReadableStreamReader)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
@@ -34,18 +62,21 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
NS_INTERFACE_MAP_END
Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse, AbortSignal* aSignal)
- : FetchBody<Response>()
- , mOwner(aGlobal)
+ : FetchBody<Response>(aGlobal)
, mInternalResponse(aInternalResponse)
, mSignal(aSignal)
{
MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable ||
aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response);
SetMimeType();
+
+ // Keep a firm grip!
+ mozilla::HoldJSObjects(this);
}
Response::~Response()
{
+ mozilla::DropJSObjects(this);
}
/* static */ already_AddRefed<Response>
@@ -106,7 +137,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
return nullptr;
}
- Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>> body;
+ Optional<fetch::ResponseBodyInit> body;
ResponseInit init;
init.mStatus = aStatus;
RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv);
@@ -127,7 +158,7 @@ Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
/*static*/ already_AddRefed<Response>
Response::Constructor(const GlobalObject& aGlobal,
- const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
+ const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& aRv)
{
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
@@ -193,29 +224,86 @@ Response::Constructor(const GlobalObject& aGlobal,
}
}
- if (aBody.WasPassed() && !aBody.Value().IsNull()) {
+ if (aBody.WasPassed()) {
if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) {
aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>();
return nullptr;
}
+ nsCString contentTypeWithCharset;
nsCOMPtr<nsIInputStream> bodyStream;
- nsCString contentType;
- uint64_t bodySize = 0;
- aRv = ExtractByteStreamFromBody(aBody.Value().Value(),
- getter_AddRefs(bodyStream),
- contentType,
- bodySize);
- if (NS_WARN_IF(aRv.Failed())) {
- return nullptr;
+ int64_t bodySize = InternalResponse::UNKNOWN_BODY_SIZE;
+
+ if (aBody.Value().IsReadableStream()) {
+ const ReadableStream& readableStream =
+ aBody.Value().GetAsReadableStream();
+
+ JS::Rooted<JSObject*> readableStreamObj(aGlobal.Context(),
+ readableStream.Obj());
+
+ if (JS::ReadableStreamIsDisturbed(readableStreamObj) ||
+ JS::ReadableStreamIsLocked(readableStreamObj) ||
+ !JS::ReadableStreamIsReadable(readableStreamObj)) {
+ aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
+ return nullptr;
+ }
+
+ r->SetReadableStreamBody(readableStreamObj);
+
+ if (JS::ReadableStreamGetMode(readableStreamObj) ==
+ JS::ReadableStreamMode::ExternalSource) {
+ // If this is a DOM generated ReadableStream, we can extract the
+ // inputStream directly.
+ void* underlyingSource = nullptr;
+ if (!JS::ReadableStreamGetExternalUnderlyingSource(aGlobal.Context(),
+ readableStreamObj,
+ &underlyingSource)) {
+ aRv.StealExceptionFromJSContext(aGlobal.Context());
+ return nullptr;
+ }
+
+ MOZ_ASSERT(underlyingSource);
+
+ aRv = FetchStream::RetrieveInputStream(underlyingSource,
+ getter_AddRefs(bodyStream));
+
+ // The releasing of the external source is needed in order to avoid an
+ // extra stream lock.
+ JS::ReadableStreamReleaseExternalUnderlyingSource(readableStreamObj);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ } else {
+ // If this is a JS-created ReadableStream, let's create a
+ // FetchStreamReader.
+ aRv = FetchStreamReader::Create(aGlobal.Context(), global,
+ getter_AddRefs(r->mFetchStreamReader),
+ getter_AddRefs(bodyStream));
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ } else {
+ uint64_t size = 0;
+ aRv = ExtractByteStreamFromBody(aBody.Value(),
+ getter_AddRefs(bodyStream),
+ contentTypeWithCharset,
+ size);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ bodySize = size;
}
+
internalResponse->SetBody(bodyStream, bodySize);
- if (!contentType.IsVoid() &&
+ if (!contentTypeWithCharset.IsVoid() &&
!internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) {
// Ignore Append() failing here.
ErrorResult error;
- internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error);
+ internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"),
+ contentTypeWithCharset, error);
error.SuppressException();
}
@@ -229,29 +317,87 @@ Response::Constructor(const GlobalObject& aGlobal,
}
already_AddRefed<Response>
-Response::Clone(ErrorResult& aRv) const
+Response::Clone(JSContext* aCx, ErrorResult& aRv)
{
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
- RefPtr<InternalResponse> ir = mInternalResponse->Clone();
- RefPtr<Response> response = new Response(mOwner, ir, mSignal);
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ JS::Rooted<JSObject*> body(aCx);
+ MaybeTeeReadableStreamBody(aCx, &body,
+ getter_AddRefs(streamReader),
+ getter_AddRefs(inputStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(body, streamReader);
+ MOZ_ASSERT_IF(body, inputStream);
+
+ RefPtr<InternalResponse> ir =
+ mInternalResponse->Clone(body
+ ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
+ RefPtr<Response> response = new Response(mOwner, ir, nullptr);
+
+ if (body) {
+ // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
+ // if this body is a native stream. In this case, the InternalResponse will
+ // have a clone of the native body and the ReadableStream will be created
+ // lazily if needed.
+ response->SetReadableStreamBody(body);
+ response->mFetchStreamReader = streamReader;
+ ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
+ }
+
return response.forget();
}
already_AddRefed<Response>
-Response::CloneUnfiltered(ErrorResult& aRv) const
+Response::CloneUnfiltered(JSContext* aCx, ErrorResult& aRv)
{
if (BodyUsed()) {
aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
return nullptr;
}
- RefPtr<InternalResponse> clone = mInternalResponse->Clone();
+ RefPtr<FetchStreamReader> streamReader;
+ nsCOMPtr<nsIInputStream> inputStream;
+
+ JS::Rooted<JSObject*> body(aCx);
+ MaybeTeeReadableStreamBody(aCx, &body,
+ getter_AddRefs(streamReader),
+ getter_AddRefs(inputStream), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT_IF(body, streamReader);
+ MOZ_ASSERT_IF(body, inputStream);
+
+ RefPtr<InternalResponse> clone =
+ mInternalResponse->Clone(body
+ ? InternalResponse::eDontCloneInputStream
+ : InternalResponse::eCloneInputStream);
+
RefPtr<InternalResponse> ir = clone->Unfiltered();
- RefPtr<Response> ref = new Response(mOwner, ir, mSignal);
+ RefPtr<Response> ref = new Response(mOwner, ir, nullptr);
+
+ if (body) {
+ // Maybe we have a body, but we receive null from MaybeTeeReadableStreamBody
+ // if this body is a native stream. In this case, the InternalResponse will
+ // have a clone of the native body and the ReadableStream will be created
+ // lazily if needed.
+ ref->SetReadableStreamBody(body);
+ ref->mFetchStreamReader = streamReader;
+ ir->SetBody(inputStream, InternalResponse::UNKNOWN_BODY_SIZE);
+ }
+
return ref.forget();
}
diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h
index fa2ced87d8..d2f885fbc7 100644
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -105,6 +105,8 @@ public:
void
GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); }
+ using FetchBody::GetBody;
+
static already_AddRefed<Response>
Error(const GlobalObject& aGlobal);
@@ -113,7 +115,7 @@ public:
static already_AddRefed<Response>
Constructor(const GlobalObject& aGlobal,
- const Optional<Nullable<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>>& aBody,
+ const Optional<fetch::ResponseBodyInit>& aBody,
const ResponseInit& aInit, ErrorResult& rv);
nsIGlobalObject* GetParentObject() const
@@ -122,10 +124,10 @@ public:
}
already_AddRefed<Response>
- Clone(ErrorResult& aRv) const;
+ Clone(JSContext* aCx, ErrorResult& aRv);
already_AddRefed<Response>
- CloneUnfiltered(ErrorResult& aRv) const;
+ CloneUnfiltered(JSContext* aCx, ErrorResult& aRv);
void
SetBody(nsIInputStream* aBody, int64_t aBodySize);
@@ -142,7 +144,6 @@ public:
private:
~Response();
- nsCOMPtr<nsIGlobalObject> mOwner;
RefPtr<InternalResponse> mInternalResponse;
// Lazily created
diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build
index d1a8a49323..f01254e042 100644
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -4,11 +4,13 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
EXPORTS.mozilla.dom += [
+ 'BodyExtractor.h',
'ChannelInfo.h',
'Fetch.h',
'FetchDriver.h',
'FetchIPCTypes.h',
'FetchObserver.h',
+ 'FetchStreamReader.h',
'FetchUtil.h',
'Headers.h',
'InternalHeaders.h',
@@ -19,11 +21,14 @@ EXPORTS.mozilla.dom += [
]
UNIFIED_SOURCES += [
+ 'BodyExtractor.cpp',
'ChannelInfo.cpp',
'Fetch.cpp',
'FetchConsumer.cpp',
'FetchDriver.cpp',
'FetchObserver.cpp',
+ 'FetchStream.cpp',
+ 'FetchStreamReader.cpp',
'FetchUtil.cpp',
'Headers.cpp',
'InternalHeaders.cpp',
diff --git a/dom/network/TCPSocketParent.cpp b/dom/network/TCPSocketParent.cpp
index 96eab44510..0e7f0e17ae 100644
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -310,7 +310,7 @@ TCPSocketParent::RecvData(const SendableData& aData,
const nsTArray<uint8_t>& buffer = aData.get_ArrayOfuint8_t();
bool ok = IPC::DeserializeArrayBuffer(autoCx, buffer, &val);
NS_ENSURE_TRUE(ok, true);
- RootedTypedArray<ArrayBuffer> data(autoCx);
+ RootedSpiderMonkeyInterface<ArrayBuffer> data(autoCx);
data.Init(&val.toObject());
Optional<uint32_t> byteLength(buffer.Length());
mSocket->SendWithTrackingNumber(autoCx, data, 0, byteLength, aTrackingNumber, rv);
diff --git a/dom/url/URLSearchParams.cpp b/dom/url/URLSearchParams.cpp
index 3a0311aabd..19cc3cda6b 100644
--- a/dom/url/URLSearchParams.cpp
+++ b/dom/url/URLSearchParams.cpp
@@ -575,9 +575,10 @@ URLSearchParams::ReadStructuredClone(JSStructuredCloneReader* aReader)
NS_IMETHODIMP
URLSearchParams::GetSendInfo(nsIInputStream** aBody, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset)
+ nsACString& aContentTypeWithCharset,
+ nsACString& aCharset)
{
- aContentType.AssignLiteral("application/x-www-form-urlencoded");
+ aContentTypeWithCharset.AssignLiteral("application/x-www-form-urlencoded;charset=UTF-8");
aCharset.AssignLiteral("UTF-8");
nsAutoString serialized;
diff --git a/dom/webidl/Fetch.webidl b/dom/webidl/Fetch.webidl
index 4b2a0af7d0..bbb1faf7f7 100644
--- a/dom/webidl/Fetch.webidl
+++ b/dom/webidl/Fetch.webidl
@@ -8,7 +8,7 @@
*/
typedef object JSON;
-typedef (ArrayBuffer or ArrayBufferView or Blob or FormData or USVString or URLSearchParams) BodyInit;
+typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) BodyInit;
[NoInterfaceObject, Exposed=(Window,Worker)]
interface Body {
@@ -24,3 +24,15 @@ interface Body {
[Throws]
Promise<USVString> text();
};
+
+// These are helper dictionaries for the parsing of a
+// getReader().read().then(data) parsing.
+// See more about how these 2 helpers are used in
+// dom/fetch/FetchStreamReader.cpp
+dictionary FetchReadableStreamReadDataDone {
+ boolean done = false;
+};
+
+dictionary FetchReadableStreamReadDataArray {
+ Uint8Array value;
+};
diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl
index 516b5415e3..5f2ac63e1e 100644
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -296,7 +296,7 @@ partial interface Navigator {
partial interface Navigator {
[Throws, Pref="beacon.enabled"]
boolean sendBeacon(DOMString url,
- optional (ArrayBufferView or Blob or DOMString or FormData)? data = null);
+ optional BodyInit? data = null);
};
partial interface Navigator {
diff --git a/dom/webidl/Response.webidl b/dom/webidl/Response.webidl
index 08f31fe29a..0f4c99dbac 100644
--- a/dom/webidl/Response.webidl
+++ b/dom/webidl/Response.webidl
@@ -7,7 +7,9 @@
* https://fetch.spec.whatwg.org/#response-class
*/
-[Constructor(optional BodyInit? body, optional ResponseInit init),
+// This should be Constructor(optional BodyInit... but BodyInit doesn't include
+// ReadableStream yet because we don't want to expose the Streams API to Request.
+[Constructor(optional (Blob or BufferSource or FormData or URLSearchParams or ReadableStream or USVString) body, optional ResponseInit init),
Exposed=(Window,Worker)]
interface Response {
[NewObject] static Response error();
@@ -30,6 +32,12 @@ interface Response {
};
Response implements Body;
+// This should be part of Body but we don't want to expose body to request yet.
+partial interface Response {
+ [GetterThrows, Func="nsContentUtils::StreamsEnabled"]
+ readonly attribute ReadableStream? body;
+};
+
dictionary ResponseInit {
unsigned short status = 200;
ByteString statusText = "OK";
diff --git a/dom/workers/RuntimeService.cpp b/dom/workers/RuntimeService.cpp
index 239207efc9..6138a3c6de 100644
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -299,6 +299,7 @@ LoadContextOptions(const char* aPrefName, void* /* aClosure */)
.setNativeRegExp(GetWorkerPref<bool>(NS_LITERAL_CSTRING("native_regexp")))
.setAsyncStack(GetWorkerPref<bool>(NS_LITERAL_CSTRING("asyncstack")))
.setWerror(GetWorkerPref<bool>(NS_LITERAL_CSTRING("werror")))
+ .setStreams(GetWorkerPref<bool>(NS_LITERAL_CSTRING("streams")))
.setExtraWarnings(GetWorkerPref<bool>(NS_LITERAL_CSTRING("strict")))
.setArrayProtoValues(GetWorkerPref<bool>(
NS_LITERAL_CSTRING("array_prototype_values")));
diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp
index 5aaac7cfae..bedf966ef1 100644
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -700,9 +700,14 @@ private:
request.SetAsUSVString().Rebind(loadInfo.mFullURL.Data(),
loadInfo.mFullURL.Length());
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
ErrorResult error;
RefPtr<Promise> cachePromise =
- mCacheCreator->Cache_()->Put(request, *response, error);
+ mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error);
if (NS_WARN_IF(error.Failed())) {
nsresult rv = error.StealNSResult();
channel->Cancel(rv);
@@ -1667,8 +1672,13 @@ CacheScriptLoader::Load(Cache* aCache)
mozilla::dom::CacheQueryOptions params;
+ // This JSContext will not end up executing JS code because here there are
+ // no ReadableStreams involved.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+
ErrorResult error;
- RefPtr<Promise> promise = aCache->Match(request, params, error);
+ RefPtr<Promise> promise = aCache->Match(jsapi.cx(), request, params, error);
if (NS_WARN_IF(error.Failed())) {
Fail(error.StealNSResult());
return;
diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp
index ce2e5e7aae..569422da25 100644
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -6,6 +6,7 @@
#include "ServiceWorkerEvents.h"
#include "nsAutoPtr.h"
+#include "nsContentUtils.h"
#include "nsIConsoleReportCollector.h"
#include "nsIHttpChannelInternal.h"
#include "nsINetworkInterceptController.h"
@@ -30,8 +31,6 @@
#include "mozilla/Move.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/BodyUtil.h"
-#include "mozilla/dom/DOMException.h"
-#include "mozilla/dom/DOMExceptionBinding.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/FetchEventBinding.h"
#include "mozilla/dom/MessagePort.h"
@@ -404,76 +403,6 @@ void RespondWithCopyComplete(void* aClosure, nsresult aStatus)
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(event));
}
-namespace {
-
-void
-ExtractErrorValues(JSContext* aCx, JS::Handle<JS::Value> aValue,
- nsACString& aSourceSpecOut, uint32_t *aLineOut,
- uint32_t *aColumnOut, nsString& aMessageOut)
-{
- MOZ_ASSERT(aLineOut);
- MOZ_ASSERT(aColumnOut);
-
- if (aValue.isObject()) {
- JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
- RefPtr<DOMException> domException;
-
- // Try to process as an Error object. Use the file/line/column values
- // from the Error as they will be more specific to the root cause of
- // the problem.
- JSErrorReport* err = obj ? JS_ErrorFromException(aCx, obj) : nullptr;
- if (err) {
- // Use xpc to extract the error message only. We don't actually send
- // this report anywhere.
- RefPtr<xpc::ErrorReport> report = new xpc::ErrorReport();
- report->Init(err,
- "<unknown>", // toString result
- false, // chrome
- 0); // window ID
-
- if (!report->mFileName.IsEmpty()) {
- CopyUTF16toUTF8(report->mFileName, aSourceSpecOut);
- *aLineOut = report->mLineNumber;
- *aColumnOut = report->mColumn;
- }
- aMessageOut.Assign(report->mErrorMsg);
- }
-
- // Next, try to unwrap the rejection value as a DOMException.
- else if(NS_SUCCEEDED(UNWRAP_OBJECT(DOMException, obj, domException))) {
-
- nsAutoString filename;
- domException->GetFilename(aCx, filename);
- if (!filename.IsEmpty()) {
- CopyUTF16toUTF8(filename, aSourceSpecOut);
- *aLineOut = domException->LineNumber(aCx);
- *aColumnOut = domException->ColumnNumber();
- }
-
- domException->GetName(aMessageOut);
- aMessageOut.AppendLiteral(": ");
-
- nsAutoString message;
- domException->GetMessageMoz(message);
- aMessageOut.Append(message);
- }
- }
-
- // If we could not unwrap a specific error type, then perform default safe
- // string conversions on primitives. Objects will result in "[Object]"
- // unfortunately.
- if (aMessageOut.IsEmpty()) {
- nsAutoJSString jsString;
- if (jsString.init(aCx, aValue)) {
- aMessageOut = jsString;
- } else {
- JS_ClearPendingException(aCx);
- }
- }
-}
-
-} // anonymous namespace
-
class MOZ_STACK_CLASS AutoCancel
{
RefPtr<RespondWithHandler> mOwner;
@@ -505,6 +434,44 @@ public:
}
}
+ // This function steals the error message from a ErrorResult.
+ void
+ SetCancelErrorResult(JSContext* aCx, ErrorResult& aRv)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(aRv.Failed());
+ MOZ_DIAGNOSTIC_ASSERT(!JS_IsExceptionPending(aCx));
+
+ // Storing the error as exception in the JSContext.
+ if (!aRv.MaybeSetPendingException(aCx)) {
+ return;
+ }
+
+ MOZ_ASSERT(!aRv.Failed());
+
+ // Let's take the pending exception.
+ JS::Rooted<JS::Value> exn(aCx);
+ if (!JS_GetPendingException(aCx, &exn)) {
+ return;
+ }
+
+ JS_ClearPendingException(aCx);
+
+ // Converting the exception in a js::ErrorReport.
+ js::ErrorReport report(aCx);
+ if (!report.init(aCx, exn, js::ErrorReport::WithSideEffects)) {
+ JS_ClearPendingException(aCx);
+ return;
+ }
+
+ MOZ_ASSERT(mOwner);
+ MOZ_ASSERT(mMessageName.EqualsLiteral("InterceptionFailedWithURL"));
+ MOZ_ASSERT(mParams.Length() == 1);
+
+ // Let's store the error message here.
+ mMessageName.Assign(report.toStringResult().c_str());
+ mParams.Clear();
+ }
+
template<typename... Params>
void SetCancelMessage(const nsACString& aMessageName, Params&&... aParams)
{
@@ -557,7 +524,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
- ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+ nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
+ valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@@ -572,7 +540,8 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t line = 0;
uint32_t column = 0;
nsString valueString;
- ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+ nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
+ valueString);
autoCancel.SetCancelMessageAndLocation(sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptedNonResponseWithURL"),
@@ -659,7 +628,12 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
ir->GetUnfilteredBody(getter_AddRefs(body));
// Errors and redirects may not have a body.
if (body) {
- response->SetBodyUsed();
+ IgnoredErrorResult error;
+ response->SetBodyUsed(aCx, error);
+ if (NS_WARN_IF(error.Failed())) {
+ autoCancel.SetCancelErrorResult(aCx, error);
+ return;
+ }
nsCOMPtr<nsIOutputStream> responseBody;
rv = mInterceptedChannel->GetResponseBody(getter_AddRefs(responseBody));
@@ -715,7 +689,8 @@ RespondWithHandler::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
uint32_t column = mRespondWithColumnNumber;
nsString valueString;
- ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column, valueString);
+ nsContentUtils::ExtractErrorValues(aCx, aValue, sourceSpec, &line, &column,
+ valueString);
::AsyncLog(mInterceptedChannel, sourceSpec, line, column,
NS_LITERAL_CSTRING("InterceptionRejectedResponseWithURL"),
@@ -853,7 +828,8 @@ public:
nsCString spec;
uint32_t line = 0;
uint32_t column = 0;
- ExtractErrorValues(aCx, aValue, spec, &line, &column, mRejectValue);
+ nsContentUtils::ExtractErrorValues(aCx, aValue, spec, &line, &column,
+ mRejectValue);
// only use the extracted location if we found one
if (!spec.IsEmpty()) {
diff --git a/dom/workers/ServiceWorkerScriptCache.cpp b/dom/workers/ServiceWorkerScriptCache.cpp
index 05be250da8..f343c35586 100644
--- a/dom/workers/ServiceWorkerScriptCache.cpp
+++ b/dom/workers/ServiceWorkerScriptCache.cpp
@@ -395,7 +395,7 @@ public:
return;
}
- WriteToCache(cache);
+ WriteToCache(aCx, cache);
return;
}
@@ -528,7 +528,7 @@ private:
}
void
- WriteToCache(Cache* aCache)
+ WriteToCache(JSContext* aCx, Cache* aCache)
{
AssertIsOnMainThread();
MOZ_ASSERT(aCache);
@@ -561,8 +561,10 @@ private:
// For now we have to wait until the Put Promise is fulfilled before we can
// continue since Cache does not yet support starting a read that is being
// written to.
- RefPtr<Promise> cachePromise = aCache->Put(request, *response, result);
+ RefPtr<Promise> cachePromise = aCache->Put(aCx, request, *response, result);
if (NS_WARN_IF(result.Failed())) {
+ // No exception here because there are no ReadableStreams involved here.
+ MOZ_ASSERT(!result.IsJSException());
MOZ_ASSERT(!result.IsErrorWithMessage());
Fail(result.StealNSResult());
return;
@@ -903,7 +905,7 @@ CompareCache::ManageCacheResult(JSContext* aCx, JS::Handle<JS::Value> aValue)
request.SetAsUSVString().Rebind(mURL.Data(), mURL.Length());
ErrorResult error;
CacheQueryOptions params;
- RefPtr<Promise> promise = cache->Match(request, params, error);
+ RefPtr<Promise> promise = cache->Match(aCx, request, params, error);
if (NS_WARN_IF(error.Failed())) {
mManager->CacheFinished(error.StealNSResult(), false);
return;
diff --git a/dom/workers/WorkerHolder.cpp b/dom/workers/WorkerHolder.cpp
index 5a8c5c4d8f..dcdcd29fa3 100644
--- a/dom/workers/WorkerHolder.cpp
+++ b/dom/workers/WorkerHolder.cpp
@@ -8,8 +8,9 @@
BEGIN_WORKERS_NAMESPACE
-WorkerHolder::WorkerHolder()
+WorkerHolder::WorkerHolder(Behavior aBehavior)
: mWorkerPrivate(nullptr)
+ , mBehavior(aBehavior)
{
}
@@ -44,6 +45,12 @@ WorkerHolder::ReleaseWorker()
ReleaseWorkerInternal();
}
+WorkerHolder::Behavior
+WorkerHolder::GetBehavior() const
+{
+ return mBehavior;
+}
+
void
WorkerHolder::ReleaseWorkerInternal()
{
diff --git a/dom/workers/WorkerHolder.h b/dom/workers/WorkerHolder.h
index 050c6f8e20..d8f3927af7 100644
--- a/dom/workers/WorkerHolder.h
+++ b/dom/workers/WorkerHolder.h
@@ -73,7 +73,12 @@ class WorkerHolder
public:
NS_DECL_OWNINGTHREAD
- WorkerHolder();
+ enum Behavior {
+ AllowIdleShutdownStart,
+ PreventIdleShutdownStart,
+ };
+
+ explicit WorkerHolder(Behavior aBehavior = PreventIdleShutdownStart);
virtual ~WorkerHolder();
bool HoldWorker(WorkerPrivate* aWorkerPrivate, Status aFailStatus);
@@ -81,6 +86,8 @@ public:
virtual bool Notify(Status aStatus) = 0;
+ Behavior GetBehavior() const;
+
protected:
void ReleaseWorkerInternal();
@@ -88,6 +95,8 @@ protected:
private:
void AssertIsOwningThread() const;
+
+ const Behavior mBehavior;
};
END_WORKERS_NAMESPACE
diff --git a/dom/workers/WorkerPrefs.h b/dom/workers/WorkerPrefs.h
index 215b375ddb..415435cf06 100644
--- a/dom/workers/WorkerPrefs.h
+++ b/dom/workers/WorkerPrefs.h
@@ -35,6 +35,7 @@ WORKER_SIMPLE_PREF("dom.serviceWorkers.testing.enabled", ServiceWorkersTestingEn
WORKER_SIMPLE_PREF("dom.serviceWorkers.openWindow.enabled", OpenWindowEnabled, OPEN_WINDOW_ENABLED)
WORKER_SIMPLE_PREF("dom.storageManager.enabled", StorageManagerEnabled, STORAGEMANAGER_ENABLED)
WORKER_SIMPLE_PREF("dom.push.enabled", PushEnabled, PUSH_ENABLED)
+WORKER_SIMPLE_PREF("dom.streams.enabled", StreamsEnabled, STREAMS_ENABLED)
WORKER_SIMPLE_PREF("dom.requestcontext.enabled", RequestContextEnabled, REQUESTCONTEXT_ENABLED)
WORKER_SIMPLE_PREF("gfx.offscreencanvas.enabled", OffscreenCanvasEnabled, OFFSCREENCANVAS_ENABLED)
WORKER_SIMPLE_PREF("dom.webkitBlink.dirPicker.enabled", WebkitBlinkDirectoryPickerEnabled, DOM_WEBKITBLINK_DIRPICKER_WEBKITBLINK)
diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp
index 622a882a65..3b3de7e3b5 100644
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -4154,6 +4154,7 @@ WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
, mDebugger(nullptr)
, mJSContext(nullptr)
, mPRThread(nullptr)
+ , mNumHoldersPreventingShutdownStart(0)
, mDebuggerEventLoopLevel(0)
, mMainThreadEventTarget(do_GetMainThread())
, mErrorHandlerRecursionCount(0)
@@ -5382,8 +5383,11 @@ WorkerPrivate::AddHolder(WorkerHolder* aHolder, Status aFailStatus)
MOZ_ASSERT(!mHolders.Contains(aHolder), "Already know about this one!");
- if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(true)) {
- return false;
+ if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
+ if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(true)) {
+ return false;
+ }
+ mNumHoldersPreventingShutdownStart += 1;
}
mHolders.AppendElement(aHolder);
@@ -5398,8 +5402,11 @@ WorkerPrivate::RemoveHolder(WorkerHolder* aHolder)
MOZ_ASSERT(mHolders.Contains(aHolder), "Didn't know about this one!");
mHolders.RemoveElement(aHolder);
- if (mHolders.IsEmpty() && !ModifyBusyCountFromWorker(false)) {
- NS_WARNING("Failed to modify busy count!");
+ if (aHolder->GetBehavior() == WorkerHolder::PreventIdleShutdownStart) {
+ mNumHoldersPreventingShutdownStart -= 1;
+ if (!mNumHoldersPreventingShutdownStart && !ModifyBusyCountFromWorker(false)) {
+ NS_WARNING("Failed to modify busy count!");
+ }
}
}
diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h
index 9effdccc9a..26afecb69b 100644
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -980,6 +980,7 @@ class WorkerPrivate : public WorkerPrivateParent<WorkerPrivate>
RefPtr<WorkerDebuggerGlobalScope> mDebuggerScope;
nsTArray<ParentType*> mChildWorkers;
nsTObserverArray<WorkerHolder*> mHolders;
+ uint32_t mNumHoldersPreventingShutdownStart;
nsTArray<nsAutoPtr<TimeoutInfo>> mTimeouts;
uint32_t mDebuggerEventLoopLevel;
RefPtr<ThrottledEventQueue> mMainThreadThrottledEventQueue;
diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp
index d97476ef61..a89af3c370 100644
--- a/dom/xhr/XMLHttpRequestMainThread.cpp
+++ b/dom/xhr/XMLHttpRequestMainThread.cpp
@@ -2298,175 +2298,6 @@ XMLHttpRequestMainThread::ChangeStateToDone()
}
}
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<nsIDocument>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- nsCOMPtr<nsIDOMDocument> domdoc(do_QueryInterface(mBody));
- NS_ENSURE_STATE(domdoc);
- aCharset.AssignLiteral("UTF-8");
-
- nsresult rv;
- nsCOMPtr<nsIStorageStream> storStream;
- rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsCOMPtr<nsIOutputStream> output;
- rv = storStream->GetOutputStream(0, getter_AddRefs(output));
- NS_ENSURE_SUCCESS(rv, rv);
-
- if (mBody->IsHTMLDocument()) {
- aContentType.AssignLiteral("text/html");
-
- nsString serialized;
- if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- nsAutoCString utf8Serialized;
- if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- uint32_t written;
- rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
- NS_ENSURE_SUCCESS(rv, rv);
-
- MOZ_ASSERT(written == utf8Serialized.Length());
- } else {
- aContentType.AssignLiteral("application/xml");
-
- nsCOMPtr<nsIDOMSerializer> serializer =
- do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID, &rv);
- NS_ENSURE_SUCCESS(rv, rv);
-
- // Make sure to use the encoding we'll send
- rv = serializer->SerializeToStream(domdoc, output, aCharset);
- NS_ENSURE_SUCCESS(rv, rv);
- }
-
- output->Close();
-
- uint32_t length;
- rv = storStream->GetLength(&length);
- NS_ENSURE_SUCCESS(rv, rv);
- *aContentLength = length;
-
- rv = storStream->NewInputStream(0, aResult);
- NS_ENSURE_SUCCESS(rv, rv);
- return NS_OK;
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<const nsAString>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- aContentType.AssignLiteral("text/plain");
- aCharset.AssignLiteral("UTF-8");
-
- nsAutoCString converted;
- if (!AppendUTF16toUTF8(*mBody, converted, fallible)) {
- return NS_ERROR_OUT_OF_MEMORY;
- }
-
- *aContentLength = converted.Length();
- nsresult rv = NS_NewCStringInputStream(aResult, converted);
- NS_ENSURE_SUCCESS(rv, rv);
- return NS_OK;
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<nsIInputStream>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- aContentType.AssignLiteral("text/plain");
- aCharset.Truncate();
-
- nsresult rv = mBody->Available(aContentLength);
- NS_ENSURE_SUCCESS(rv, rv);
-
- nsCOMPtr<nsIInputStream> stream(mBody);
- stream.forget(aResult);
- return NS_OK;
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<Blob>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<FormData>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<URLSearchParams>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<nsIXHRSendable>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- return mBody->GetSendInfo(aResult, aContentLength, aContentType, aCharset);
-}
-
-static nsresult
-GetBufferDataAsStream(const uint8_t* aData, uint32_t aDataLength,
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset)
-{
- aContentType.SetIsVoid(true);
- aCharset.Truncate();
-
- *aContentLength = aDataLength;
- const char* data = reinterpret_cast<const char*>(aData);
-
- nsCOMPtr<nsIInputStream> stream;
- nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), data, aDataLength,
- NS_ASSIGNMENT_COPY);
- NS_ENSURE_SUCCESS(rv, rv);
-
- stream.forget(aResult);
-
- return NS_OK;
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<const ArrayBuffer>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- mBody->ComputeLengthAndData();
- return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
- aResult, aContentLength, aContentType, aCharset);
-}
-
-template<> nsresult
-XMLHttpRequestMainThread::RequestBody<const ArrayBufferView>::GetAsStream(
- nsIInputStream** aResult, uint64_t* aContentLength,
- nsACString& aContentType, nsACString& aCharset) const
-{
- mBody->ComputeLengthAndData();
- return GetBufferDataAsStream(mBody->Data(), mBody->Length(),
- aResult, aContentLength, aContentType, aCharset);
-}
-
-
nsresult
XMLHttpRequestMainThread::CreateChannel()
{
@@ -2792,7 +2623,7 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
// document?
nsCOMPtr<nsIDocument> doc = do_QueryInterface(supports);
if (doc) {
- RequestBody<nsIDocument> body(doc);
+ BodyExtractor<nsIDocument> body(doc);
return SendInternal(&body);
}
@@ -2801,21 +2632,21 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
if (wstr) {
nsAutoString string;
wstr->GetData(string);
- RequestBody<const nsAString> body(&string);
+ BodyExtractor<const nsAString> body(&string);
return SendInternal(&body);
}
// nsIInputStream?
nsCOMPtr<nsIInputStream> stream = do_QueryInterface(supports);
if (stream) {
- RequestBody<nsIInputStream> body(stream);
+ BodyExtractor<nsIInputStream> body(stream);
return SendInternal(&body);
}
// nsIXHRSendable?
nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
if (sendable) {
- RequestBody<nsIXHRSendable> body(sendable);
+ BodyExtractor<nsIXHRSendable> body(sendable);
return SendInternal(&body);
}
@@ -2826,9 +2657,9 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
nsresult rv = aVariant->GetAsJSVal(&realVal);
if (NS_SUCCEEDED(rv) && !realVal.isPrimitive()) {
JS::Rooted<JSObject*> obj(rootingCx, realVal.toObjectOrNull());
- RootedTypedArray<ArrayBuffer> buf(rootingCx);
+ RootedSpiderMonkeyInterface<ArrayBuffer> buf(rootingCx);
if (buf.Init(obj)) {
- RequestBody<const ArrayBuffer> body(&buf);
+ BodyExtractor<const ArrayBuffer> body(&buf);
return SendInternal(&body);
}
}
@@ -2845,12 +2676,12 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant)
nsString string;
string.Adopt(data, len);
- RequestBody<const nsAString> body(&string);
+ BodyExtractor<const nsAString> body(&string);
return SendInternal(&body);
}
nsresult
-XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
+XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody)
{
NS_ENSURE_TRUE(mPrincipal, NS_ERROR_NOT_INITIALIZED);
@@ -2916,13 +2747,6 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
mAuthorRequestHeaders.Get("content-type", uploadContentType);
if (uploadContentType.IsVoid()) {
uploadContentType = defaultContentType;
-
- if (!charset.IsEmpty()) {
- // If we are providing the default content type, then we also need to
- // provide a charset declaration.
- uploadContentType.Append(NS_LITERAL_CSTRING(";charset="));
- uploadContentType.Append(charset);
- }
}
// We don't want to set a charset for streams.
diff --git a/dom/xhr/XMLHttpRequestMainThread.h b/dom/xhr/XMLHttpRequestMainThread.h
index 4a6eeec2ef..f2145605ef 100644
--- a/dom/xhr/XMLHttpRequestMainThread.h
+++ b/dom/xhr/XMLHttpRequestMainThread.h
@@ -34,7 +34,11 @@
#include "mozilla/MemoryReporting.h"
#include "mozilla/NotNull.h"
#include "mozilla/dom/MutableBlobStorage.h"
+#include "mozilla/dom/BodyExtractor.h"
#include "mozilla/dom/TypedArray.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/FormData.h"
+#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "mozilla/dom/XMLHttpRequestBinding.h"
#include "mozilla/dom/XMLHttpRequestEventTarget.h"
@@ -54,11 +58,8 @@ class nsIJSID;
namespace mozilla {
namespace dom {
-class Blob;
class BlobSet;
class DOMString;
-class FormData;
-class URLSearchParams;
class XMLHttpRequestUpload;
struct OriginAttributesDictionary;
@@ -288,34 +289,7 @@ public:
private:
virtual ~XMLHttpRequestMainThread();
- class RequestBodyBase
- {
- public:
- virtual nsresult GetAsStream(nsIInputStream** aResult,
- uint64_t* aContentLength,
- nsACString& aContentType,
- nsACString& aCharset) const
- {
- NS_ASSERTION(false, "RequestBodyBase should not be used directly.");
- return NS_ERROR_FAILURE;
- }
- };
-
- template<typename Type>
- class RequestBody final : public RequestBodyBase
- {
- Type* mBody;
- public:
- explicit RequestBody(Type* aBody) : mBody(aBody)
- {
- }
- nsresult GetAsStream(nsIInputStream** aResult,
- uint64_t* aContentLength,
- nsACString& aContentType,
- nsACString& aCharset) const override;
- };
-
- nsresult SendInternal(const RequestBodyBase* aBody);
+ nsresult SendInternal(const BodyExtractorBase* aBody);
bool IsCrossSiteCORSRequest() const;
bool IsDeniedCrossSiteCORSRequest();
@@ -336,7 +310,7 @@ public:
Send(JSContext* /*aCx*/, const ArrayBuffer& aArrayBuffer,
ErrorResult& aRv) override
{
- RequestBody<const ArrayBuffer> body(&aArrayBuffer);
+ BodyExtractor<const ArrayBuffer> body(&aArrayBuffer);
aRv = SendInternal(&body);
}
@@ -344,28 +318,28 @@ public:
Send(JSContext* /*aCx*/, const ArrayBufferView& aArrayBufferView,
ErrorResult& aRv) override
{
- RequestBody<const ArrayBufferView> body(&aArrayBufferView);
+ BodyExtractor<const ArrayBufferView> body(&aArrayBufferView);
aRv = SendInternal(&body);
}
virtual void
Send(JSContext* /*aCx*/, Blob& aBlob, ErrorResult& aRv) override
{
- RequestBody<Blob> body(&aBlob);
+ BodyExtractor<nsIXHRSendable> body(&aBlob);
aRv = SendInternal(&body);
}
virtual void Send(JSContext* /*aCx*/, URLSearchParams& aURLSearchParams,
ErrorResult& aRv) override
{
- RequestBody<URLSearchParams> body(&aURLSearchParams);
+ BodyExtractor<nsIXHRSendable> body(&aURLSearchParams);
aRv = SendInternal(&body);
}
virtual void
Send(JSContext* /*aCx*/, nsIDocument& aDoc, ErrorResult& aRv) override
{
- RequestBody<nsIDocument> body(&aDoc);
+ BodyExtractor<nsIDocument> body(&aDoc);
aRv = SendInternal(&body);
}
@@ -375,7 +349,7 @@ public:
if (DOMStringIsNull(aString)) {
Send(aCx, aRv);
} else {
- RequestBody<const nsAString> body(&aString);
+ BodyExtractor<const nsAString> body(&aString);
aRv = SendInternal(&body);
}
}
@@ -383,7 +357,7 @@ public:
virtual void
Send(JSContext* /*aCx*/, FormData& aFormData, ErrorResult& aRv) override
{
- RequestBody<FormData> body(&aFormData);
+ BodyExtractor<nsIXHRSendable> body(&aFormData);
aRv = SendInternal(&body);
}
@@ -391,7 +365,7 @@ public:
Send(JSContext* aCx, nsIInputStream* aStream, ErrorResult& aRv) override
{
NS_ASSERTION(aStream, "Null should go to string version");
- RequestBody<nsIInputStream> body(aStream);
+ BodyExtractor<nsIInputStream> body(aStream);
aRv = SendInternal(&body);
}
diff --git a/dom/xhr/nsIXMLHttpRequest.idl b/dom/xhr/nsIXMLHttpRequest.idl
index 53e80bab70..5505bd47ee 100644
--- a/dom/xhr/nsIXMLHttpRequest.idl
+++ b/dom/xhr/nsIXMLHttpRequest.idl
@@ -326,9 +326,12 @@ interface nsIXMLHttpRequest : nsISupports
[uuid(840d0d00-e83e-4a29-b3c7-67e96e90a499)]
interface nsIXHRSendable : nsISupports {
+ // contentTypeWithCharset can be set to the contentType or
+ // contentType+charset based on what the spec says.
+ // See: https://fetch.spec.whatwg.org/#concept-bodyinit-extract
void getSendInfo(out nsIInputStream body,
out uint64_t contentLength,
- out ACString contentType,
+ out ACString contentTypeWithCharset,
out ACString charset);
};
diff --git a/js/public/Stream.h b/js/public/Stream.h
new file mode 100644
index 0000000000..b91239ac9a
--- /dev/null
+++ b/js/public/Stream.h
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * JSAPI functions and callbacks related to WHATWG Stream objects.
+ *
+ * Much of the API here mirrors the JS API of ReadableStream and associated
+ * classes, e.g. ReadableStreamDefaultReader, ReadableStreamBYOBReader,
+ * ReadableStreamDefaultController, ReadableByteStreamController, and
+ * ReadableStreamBYOBRequest.
+ *
+ * There are some crucial differences, though: Functionality that's exposed
+ * as methods/accessors on controllers in JS is exposed as functions taking
+ * ReadableStream instances instead. This is because an analysis of how
+ * the API would be used showed that all functions that'd take controllers
+ * would do so by first getting the controller from the stream instance it's
+ * associated with and then call the function taking it. I.e., it would purely
+ * add boilerplate without any gains in ease of use of the API.
+ *
+ * Some functions exposed here deal with ReadableStream instances that have an
+ * embedding-provided underlying source. These instances are largely similar
+ * to byte streams as created using |new ReadableStream({type: "bytes"})|:
+ * They enable users to acquire ReadableStreamBYOBReaders and only vend chunks
+ * that're typed array instances.
+ *
+ * When creating an "external readable stream" using
+ * JS::NewReadableExternalSourceStreamObject, an underlying source and a set
+ * of flags 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. Similarly, the flags aren't
+ * interpreted by the JS engine, but are passed to some of the callbacks below
+ * and can be retrieved using JS::ReadableStreamGetEmbeddingFlags.
+ *
+ * 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 array instances; instead, the embedding only updates the amount of
+ * data available using ReadableStreamUpdateDataAvailableFromSource.
+ * When content requests data by reading from a reader,
+ * WriteIntoReadRequestBufferCallback is invoked, asking the embedding to
+ * write data directly into the buffer we're about to hand to content.
+ *
+ * Additionally, ReadableStreamGetExternalUnderlyingSource can be used to
+ * get the void* pointer to the underlying source. This is equivalent to
+ * acquiring a reader for the stream in that it locks the stream until it
+ * is released again using JS::ReadableStreamReleaseExternalUnderlyingSource.
+ *
+ * Embeddings are expected to detect situations where an API exposed to JS
+ * takes a ReadableStream to read from that has an external underlying source.
+ * In those situations, it might be preferable to directly perform data
+ * transfers from the stream's underlying source to whatever sink the
+ * embedding uses, assuming that such direct transfers can be performed
+ * more efficiently.
+ */
+
+#ifndef js_Stream_h
+#define js_Stream_h
+
+#include "jstypes.h"
+
+#include "js/TypeDecls.h"
+
+namespace JS {
+
+/**
+ * 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, uint8_t flags, size_t desiredSize);
+
+/**
+ * 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, uint8_t flags, void* buffer,
+ size_t length, size_t* bytesWritten);
+
+/**
+ * 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, uint8_t flags, 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,
+ uint8_t flags);
+
+/**
+ * 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,
+ uint8_t flags, HandleValue reason);
+
+/**
+ * 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, uint8_t flags);
+
+/**
+ * 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);
+
+/**
+ * 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*)
+NewReadableDefaultStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
+ HandleFunction size = nullptr, double highWaterMark = 1,
+ HandleObject proto = nullptr);
+
+/**
+ * Returns a new instance of the ReadableStream builtin class in the current
+ * compartment, configured as a byte 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*)
+NewReadableByteStreamObject(JSContext* cx, HandleObject underlyingSource = nullptr,
+ double highWaterMark = 0, HandleObject proto = nullptr);
+
+/**
+ * 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|.
+ *
+ * The given |flags| will be passed to all applicable callbacks and can be
+ * used to disambiguate between different types of stream sources the
+ * embedding might support.
+ *
+ * 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.
+ */
+extern JS_PUBLIC_API(JSObject*)
+NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
+ uint8_t flags = 0, HandleObject proto = nullptr);
+
+/**
+ * Returns the flags that were passed to NewReadableExternalSourceStreamObject
+ * when creating the given stream.
+ *
+ * Asserts that the given stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(uint8_t)
+ReadableStreamGetEmbeddingFlags(const JSObject* stream);
+
+/**
+ * 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
+ * with the stream at all.
+ *
+ * Locks the stream until ReadableStreamReleaseExternalUnderlyingSource is
+ * called.
+ *
+ * Throws an exception if the stream is locked, i.e. if a reader has been
+ * acquired for the stream, or if ReadableStreamGetExternalUnderlyingSource
+ * has been used previously without releasing the external source again.
+ *
+ * Throws an exception if the stream isn't readable, i.e if it is errored or
+ * closed. This is different from ReadableStreamGetReader because we don't
+ * have a Promise to resolve/reject, which a reader provides.
+ *
+ * Asserts that the stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject stream, void** 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.
+ *
+ * Asserts that the stream has an embedding-provided underlying source.
+ */
+extern JS_PUBLIC_API(void)
+ReadableStreamReleaseExternalUnderlyingSource(JSObject* 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.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, HandleObject stream,
+ uint32_t availableData);
+
+/**
+ * Returns true if the given object is an unwrapped ReadableStream object,
+ * false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStream(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamDefaultReader or ReadableStreamBYOBReader object,
+ * false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamReader(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamDefaultReader object, false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamDefaultReader(const JSObject* obj);
+
+/**
+ * Returns true if the given object is an unwrapped
+ * ReadableStreamBYOBReader object, false otherwise.
+ */
+extern JS_PUBLIC_API(bool)
+IsReadableStreamBYOBReader(const JSObject* obj);
+
+enum class ReadableStreamMode {
+ Default,
+ Byte,
+ ExternalSource
+};
+
+/**
+ * Returns the stream's ReadableStreamMode. If the mode is |Byte| or
+ * |ExternalSource|, it's possible to acquire a BYOB reader for more optimized
+ * operations.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(ReadableStreamMode)
+ReadableStreamGetMode(const JSObject* stream);
+
+enum class ReadableStreamReaderMode {
+ Default,
+ BYOB
+};
+
+/**
+ * Returns true if the given ReadableStream is readable, false if not.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsReadable(const JSObject* stream);
+
+/**
+ * Returns true if the given ReadableStream is locked, false if not.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsLocked(const JSObject* stream);
+
+/**
+ * Returns true if the given ReadableStream is disturbed, false if not.
+ *
+ * Asserts that |stream| is an ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamIsDisturbed(const JSObject* stream);
+
+/**
+ * Cancels the given ReadableStream with the given reason and returns a
+ * Promise resolved according to the result.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamCancel(JSContext* cx, HandleObject stream, HandleValue reason);
+
+/**
+ * Creates a reader of the type specified by the mode option and locks the
+ * stream to the new reader.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamGetReader(JSContext* cx, HandleObject stream, ReadableStreamReaderMode mode);
+
+/**
+ * Tees the given ReadableStream and stores the two resulting streams in
+ * outparams. Returns false if the operation fails, e.g. because the stream is
+ * locked.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamTee(JSContext* cx, HandleObject stream,
+ MutableHandleObject branch1Stream, MutableHandleObject branch2Stream);
+
+/**
+ * Retrieves the desired combined size of additional chunks to fill the given
+ * ReadableStream's queue. Stores the result in |value| and sets |hasValue| to
+ * true on success, returns false on failure.
+ *
+ * If the stream is errored, the call will succeed but no value will be stored
+ * in |value| and |hasValue| will be set to false.
+ *
+ * Note: This is semantically equivalent to the |desiredSize| getter on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(void)
+ReadableStreamGetDesiredSize(JSObject* stream, bool* hasValue, double* value);
+
+/**
+ * Closes the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the closing operation fails.
+ *
+ * Note: This is semantically equivalent to the |close| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamClose(JSContext* cx, HandleObject stream);
+
+/**
+ * Returns true if the given ReadableStream reader is locked, false otherwise.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderIsClosed(const JSObject* reader);
+
+/**
+ * Enqueues the given chunk in the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the enqueing operation fails.
+ *
+ * Note: This is semantically equivalent to the |enqueue| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * If the ReadableStream has an underlying byte source, the given chunk must
+ * be a typed array or a DataView. Consider using
+ * ReadableByteStreamEnqueueBuffer.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamEnqueue(JSContext* cx, HandleObject stream, HandleValue chunk);
+
+/**
+ * Enqueues the given buffer as a chunk in the given ReadableStream.
+ *
+ * Throws a TypeError and returns false if the enqueing operation fails.
+ *
+ * Note: This is semantically equivalent to the |enqueue| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity. Additionally, the JS version only
+ * takes typed arrays and ArrayBufferView instances as arguments, whereas
+ * this takes an ArrayBuffer, obviating the need to wrap it into a typed
+ * array.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance and |buffer|
+ * an unwrapped ArrayBuffer instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject stream, HandleObject buffer);
+
+/**
+ * Errors the given ReadableStream, causing all future interactions to fail
+ * with the given error value.
+ *
+ * Throws a TypeError and returns false if the erroring operation fails.
+ *
+ * Note: This is semantically equivalent to the |error| method on
+ * the stream controller's prototype in JS. We expose it with the stream
+ * itself as a target for simplicity.
+ *
+ * Asserts that |stream| is an unwrapped ReadableStream instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamError(JSContext* cx, HandleObject stream, HandleValue error);
+
+/**
+ * Cancels the given ReadableStream reader's associated stream.
+ *
+ * Throws a TypeError and returns false if the given reader isn't active.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason);
+
+/**
+ * Cancels the given ReadableStream reader's associated stream.
+ *
+ * Throws a TypeError and returns false if the given reader has pending
+ * read or readInto (for default or byob readers, respectively) requests.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader or
+ * ReadableStreamBYOBReader instance.
+ */
+extern JS_PUBLIC_API(bool)
+ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
+
+/**
+ * Requests a read from the reader's associated ReadableStream and returns the
+ * resulting PromiseObject.
+ *
+ * Returns a Promise that's resolved with the read result once available or
+ * rejected immediately if the stream is errored or the operation failed.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject reader);
+
+/**
+ * Requests a read from the reader's associated ReadableStream into the given
+ * ArrayBufferView and returns the resulting PromiseObject.
+ *
+ * Returns a Promise that's resolved with the read result once available or
+ * rejected immediately if the stream is errored or the operation failed.
+ *
+ * Asserts that |reader| is an unwrapped ReadableStreamDefaultReader and
+ * |view| an unwrapped typed array or DataView instance.
+ */
+extern JS_PUBLIC_API(JSObject*)
+ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject reader, HandleObject view);
+
+} // namespace JS
+
+#endif // js_Realm_h
diff --git a/js/src/builtin/Stream.cpp b/js/src/builtin/Stream.cpp
new file mode 100644
index 0000000000..c8d8e3e324
--- /dev/null
+++ b/js/src/builtin/Stream.cpp
@@ -0,0 +1,5488 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "builtin/Stream.h"
+
+#include "js/Stream.h"
+
+#include "jscntxt.h"
+
+#include "gc/Heap.h"
+#include "vm/SelfHosting.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+
+using namespace js;
+
+enum StreamSlots {
+ StreamSlot_Controller,
+ StreamSlot_Reader,
+ StreamSlot_State,
+ StreamSlot_StoredError,
+ StreamSlotCount
+};
+
+enum ReaderSlots {
+ ReaderSlot_Stream,
+ ReaderSlot_Requests,
+ ReaderSlot_ClosedPromise,
+ ReaderSlotCount,
+};
+
+enum ReaderType {
+ ReaderType_Default,
+ ReaderType_BYOB
+};
+
+// ReadableStreamDefaultController and ReadableByteStreamController are both
+// queue containers and must have these slots at identical offsets.
+enum QueueContainerSlots {
+ QueueContainerSlot_Queue,
+ QueueContainerSlot_TotalSize,
+ QueueContainerSlotCount
+};
+
+// These slots are identical between the two types of ReadableStream
+// controllers.
+enum ControllerSlots {
+ ControllerSlot_Stream = QueueContainerSlotCount,
+ ControllerSlot_UnderlyingSource,
+ ControllerSlot_StrategyHWM,
+ ControllerSlot_Flags,
+ ControllerSlotCount
+};
+
+enum DefaultControllerSlots {
+ DefaultControllerSlot_StrategySize = ControllerSlotCount,
+ DefaultControllerSlotCount
+};
+
+enum ByteControllerSlots {
+ ByteControllerSlot_BYOBRequest = ControllerSlotCount,
+ ByteControllerSlot_PendingPullIntos,
+ ByteControllerSlot_AutoAllocateSize,
+ ByteControllerSlotCount
+};
+
+enum ControllerFlags {
+ ControllerFlag_Started = 1 << 0,
+ ControllerFlag_Pulling = 1 << 1,
+ ControllerFlag_PullAgain = 1 << 2,
+ ControllerFlag_CloseRequested = 1 << 3,
+ ControllerFlag_TeeBranch = 1 << 4,
+ ControllerFlag_TeeBranch1 = 1 << 5,
+ ControllerFlag_TeeBranch2 = 1 << 6,
+ ControllerFlag_ExternalSource = 1 << 7,
+ ControllerFlag_SourceLocked = 1 << 8,
+};
+
+// Offset at which embedding flags are stored.
+constexpr uint8_t ControllerEmbeddingFlagsOffset = 24;
+
+enum BYOBRequestSlots {
+ BYOBRequestSlot_Controller,
+ BYOBRequestSlot_View,
+ BYOBRequestSlotCount
+};
+
+template<class T>
+MOZ_ALWAYS_INLINE bool
+Is(const HandleValue v)
+{
+ return v.isObject() && v.toObject().is<T>();
+}
+
+#ifdef DEBUG
+static bool
+IsReadableStreamController(const JSObject* controller)
+{
+ return controller->is<ReadableStreamDefaultController>() ||
+ controller->is<ReadableByteStreamController>();
+}
+#endif // DEBUG
+
+static inline uint32_t
+ControllerFlags(const NativeObject* controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ return controller->getFixedSlot(ControllerSlot_Flags).toInt32();
+}
+
+static inline void
+AddControllerFlags(NativeObject* controller, uint32_t flags)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ controller->setFixedSlot(ControllerSlot_Flags,
+ Int32Value(ControllerFlags(controller) | flags));
+}
+
+static inline void
+RemoveControllerFlags(NativeObject* controller, uint32_t flags)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ controller->setFixedSlot(ControllerSlot_Flags,
+ Int32Value(ControllerFlags(controller) & ~flags));
+}
+
+static inline uint32_t
+StreamState(const ReadableStream* stream)
+{
+ return stream->getFixedSlot(StreamSlot_State).toInt32();
+}
+
+static inline void
+SetStreamState(ReadableStream* stream, uint32_t state)
+{
+ MOZ_ASSERT_IF(stream->disturbed(), state & ReadableStream::Disturbed);
+ MOZ_ASSERT_IF(stream->closed() || stream->errored(), !(state & ReadableStream::Readable));
+ stream->setFixedSlot(StreamSlot_State, Int32Value(state));
+}
+
+bool
+ReadableStream::readable() const
+{
+ return StreamState(this) & Readable;
+}
+
+bool
+ReadableStream::closed() const
+{
+ return StreamState(this) & Closed;
+}
+
+bool
+ReadableStream::errored() const
+{
+ return StreamState(this) & Errored;
+}
+
+bool
+ReadableStream::disturbed() const
+{
+ return StreamState(this) & Disturbed;
+}
+
+inline static bool
+ReaderHasStream(const NativeObject* reader)
+{
+ MOZ_ASSERT(JS::IsReadableStreamReader(reader));
+ return !reader->getFixedSlot(ReaderSlot_Stream).isUndefined();
+}
+
+bool
+js::ReadableStreamReaderIsClosed(const JSObject* reader)
+{
+ return !ReaderHasStream(&reader->as<NativeObject>());
+}
+
+inline static MOZ_MUST_USE ReadableStream*
+StreamFromController(const NativeObject* controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+ return &controller->getFixedSlot(ControllerSlot_Stream).toObject().as<ReadableStream>();
+}
+
+inline static MOZ_MUST_USE NativeObject*
+ControllerFromStream(const ReadableStream* stream)
+{
+ Value controllerVal = stream->getFixedSlot(StreamSlot_Controller);
+ MOZ_ASSERT(IsReadableStreamController(&controllerVal.toObject()));
+ return &controllerVal.toObject().as<NativeObject>();
+}
+
+inline static bool
+HasController(const ReadableStream* stream)
+{
+ return !stream->getFixedSlot(StreamSlot_Controller).isUndefined();
+}
+
+JS::ReadableStreamMode
+ReadableStream::mode() const
+{
+ NativeObject* controller = ControllerFromStream(this);
+ if (controller->is<ReadableStreamDefaultController>())
+ return JS::ReadableStreamMode::Default;
+ return controller->as<ReadableByteStreamController>().hasExternalSource()
+ ? JS::ReadableStreamMode::ExternalSource
+ : JS::ReadableStreamMode::Byte;
+}
+
+inline static MOZ_MUST_USE ReadableStream*
+StreamFromReader(const NativeObject* reader)
+{
+ MOZ_ASSERT(ReaderHasStream(reader));
+ return &reader->getFixedSlot(ReaderSlot_Stream).toObject().as<ReadableStream>();
+}
+
+inline static MOZ_MUST_USE NativeObject*
+ReaderFromStream(const NativeObject* stream)
+{
+ Value readerVal = stream->getFixedSlot(StreamSlot_Reader);
+ MOZ_ASSERT(JS::IsReadableStreamReader(&readerVal.toObject()));
+ return &readerVal.toObject().as<NativeObject>();
+}
+
+inline static bool
+HasReader(const ReadableStream* stream)
+{
+ return !stream->getFixedSlot(StreamSlot_Reader).isUndefined();
+}
+
+inline static MOZ_MUST_USE JSFunction*
+NewHandler(JSContext *cx, Native handler, HandleObject target)
+{
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction handlerFun(cx, NewNativeFunction(cx, handler, 0, funName,
+ gc::AllocKind::FUNCTION_EXTENDED,
+ GenericObject));
+ if (!handlerFun)
+ return nullptr;
+ handlerFun->setExtendedSlot(0, ObjectValue(*target));
+ return handlerFun;
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+TargetFromHandler(JSObject& handler)
+{
+ return &handler.as<JSFunction>().getExtendedSlot(0).toObject().as<T>();
+}
+
+inline static MOZ_MUST_USE bool
+ResetQueue(JSContext* cx, HandleNativeObject container);
+
+inline static MOZ_MUST_USE bool
+InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
+ MutableHandleValue rval);
+
+static MOZ_MUST_USE JSObject*
+PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg);
+
+static MOZ_MUST_USE JSObject*
+PromiseRejectedWithPendingError(JSContext* cx) {
+ // Not much we can do about uncatchable exceptions, just bail.
+ RootedValue exn(cx);
+ if (!GetAndClearException(cx, &exn))
+ return nullptr;
+ return PromiseObject::unforgeableReject(cx, exn);
+}
+
+static bool
+ReportArgTypeError(JSContext* cx, const char* funName, const char* expectedType,
+ HandleValue arg)
+{
+ UniqueChars bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, arg, nullptr);
+ if (!bytes)
+ return false;
+
+ return JS_ReportErrorFlagsAndNumberLatin1(cx, JSREPORT_ERROR, 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;
+
+ args.rval().setObject(*promise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+RejectNonGenericMethod(JSContext* cx, const CallArgs& args,
+ const char* className, const char* methodName)
+{
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, className, methodName);
+
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+}
+
+inline static MOZ_MUST_USE NativeObject*
+SetNewList(JSContext* cx, HandleNativeObject container, uint32_t slot)
+{
+ NativeObject* list = NewObjectWithNullTaggedProto<PlainObject>(cx);
+ if (!list)
+ return nullptr;
+ container->setFixedSlot(slot, ObjectValue(*list));
+ return list;
+}
+
+inline static MOZ_MUST_USE bool
+AppendToList(JSContext* cx, HandleNativeObject list, HandleValue value)
+{
+ uint32_t length = list->getDenseInitializedLength();
+
+ if (!list->ensureElements(cx, length + 1))
+ return false;
+
+ list->ensureDenseInitializedLength(cx, length, 1);
+ list->setDenseElement(length, value);
+
+ return true;
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+PeekList(NativeObject* list)
+{
+ MOZ_ASSERT(list->getDenseInitializedLength() > 0);
+ return &list->getDenseElement(0).toObject().as<T>();
+}
+
+template<class T>
+inline static MOZ_MUST_USE T*
+ShiftFromList(JSContext* cx, HandleNativeObject list)
+{
+ uint32_t length = list->getDenseInitializedLength();
+ MOZ_ASSERT(length > 0);
+
+ Rooted<T*> entry(cx, &list->getDenseElement(0).toObject().as<T>());
+ list->moveDenseElements(0, 1, length - 1);
+ list->shrinkElements(cx, length - 1);
+
+ list->setDenseInitializedLength(length - 1);
+
+ return entry;
+}
+
+class ByteStreamChunk : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Buffer = 0,
+ Slot_ByteOffset,
+ Slot_ByteLength,
+ SlotCount
+ };
+
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* buffer() {
+ return &getFixedSlot(Slot_Buffer).toObject().as<ArrayBufferObject>();
+ }
+ uint32_t byteOffset() { return getFixedSlot(Slot_ByteOffset).toInt32(); }
+ void SetByteOffset(uint32_t offset) {
+ setFixedSlot(Slot_ByteOffset, Int32Value(offset));
+ }
+ uint32_t byteLength() { return getFixedSlot(Slot_ByteLength).toInt32(); }
+ void SetByteLength(uint32_t length) {
+ setFixedSlot(Slot_ByteLength, Int32Value(length));
+ }
+
+ static ByteStreamChunk* create(JSContext* cx, HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength)
+ {
+ Rooted<ByteStreamChunk*> chunk(cx, NewObjectWithClassProto<ByteStreamChunk>(cx));
+ if (!chunk)
+ return nullptr;
+
+ chunk->setFixedSlot(Slot_Buffer, ObjectValue(*buffer));
+ chunk->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
+ chunk->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
+ return chunk;
+ }
+};
+
+const Class ByteStreamChunk::class_ = {
+ "ByteStreamChunk",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class PullIntoDescriptor : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_buffer,
+ Slot_ByteOffset,
+ Slot_ByteLength,
+ Slot_BytesFilled,
+ Slot_ElementSize,
+ Slot_Ctor,
+ Slot_ReaderType,
+ SlotCount
+ };
+ public:
+ static const Class class_;
+
+ ArrayBufferObject* buffer() {
+ return &getFixedSlot(Slot_buffer).toObject().as<ArrayBufferObject>();
+ }
+ void setBuffer(ArrayBufferObject* buffer) { setFixedSlot(Slot_buffer, ObjectValue(*buffer)); }
+ JSObject* ctor() { return getFixedSlot(Slot_Ctor).toObjectOrNull(); }
+ uint32_t byteOffset() const { return getFixedSlot(Slot_ByteOffset).toInt32(); }
+ uint32_t byteLength() const { return getFixedSlot(Slot_ByteLength).toInt32(); }
+ uint32_t bytesFilled() const { return getFixedSlot(Slot_BytesFilled).toInt32(); }
+ void setBytesFilled(int32_t bytes) { setFixedSlot(Slot_BytesFilled, Int32Value(bytes)); }
+ uint32_t elementSize() const { return getFixedSlot(Slot_ElementSize).toInt32(); }
+ uint32_t readerType() const { return getFixedSlot(Slot_ReaderType).toInt32(); }
+
+ static PullIntoDescriptor* create(JSContext* cx, HandleArrayBufferObject buffer,
+ uint32_t byteOffset, uint32_t byteLength,
+ uint32_t bytesFilled, uint32_t elementSize,
+ HandleObject ctor, uint32_t readerType)
+ {
+ Rooted<PullIntoDescriptor*> descriptor(cx, NewObjectWithClassProto<PullIntoDescriptor>(cx));
+ if (!descriptor)
+ return nullptr;
+
+ descriptor->setFixedSlot(Slot_buffer, ObjectValue(*buffer));
+ descriptor->setFixedSlot(Slot_Ctor, ObjectOrNullValue(ctor));
+ descriptor->setFixedSlot(Slot_ByteOffset, Int32Value(byteOffset));
+ descriptor->setFixedSlot(Slot_ByteLength, Int32Value(byteLength));
+ descriptor->setFixedSlot(Slot_BytesFilled, Int32Value(bytesFilled));
+ descriptor->setFixedSlot(Slot_ElementSize, Int32Value(elementSize));
+ descriptor->setFixedSlot(Slot_ReaderType, Int32Value(readerType));
+ return descriptor;
+ }
+};
+
+const Class PullIntoDescriptor::class_ = {
+ "PullIntoDescriptor",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class QueueEntry : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Value = 0,
+ Slot_Size,
+ SlotCount
+ };
+
+ public:
+ static const Class class_;
+
+ Value value() { return getFixedSlot(Slot_Value); }
+ double size() { return getFixedSlot(Slot_Size).toNumber(); }
+
+ static QueueEntry* create(JSContext* cx, HandleValue value, double size)
+ {
+ Rooted<QueueEntry*> entry(cx, NewObjectWithClassProto<QueueEntry>(cx));
+ if (!entry)
+ return nullptr;
+
+ entry->setFixedSlot(Slot_Value, value);
+ entry->setFixedSlot(Slot_Size, NumberValue(size));
+
+ return entry;
+ }
+};
+
+const Class QueueEntry::class_ = {
+ "QueueEntry",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+class TeeState : public NativeObject
+{
+ private:
+ enum Slots {
+ Slot_Flags = 0,
+ Slot_Reason1,
+ Slot_Reason2,
+ Slot_Promise,
+ Slot_Stream,
+ Slot_Branch1,
+ Slot_Branch2,
+ SlotCount
+ };
+
+ enum Flags
+ {
+ Flag_ClosedOrErrored = 1 << 0,
+ Flag_Canceled1 = 1 << 1,
+ Flag_Canceled2 = 1 << 2,
+ Flag_CloneForBranch2 = 1 << 3,
+ };
+ uint32_t flags() const { return getFixedSlot(Slot_Flags).toInt32(); }
+ void setFlags(uint32_t flags) { setFixedSlot(Slot_Flags, Int32Value(flags)); }
+
+ public:
+ static const Class class_;
+
+ bool cloneForBranch2() const { return flags() & Flag_CloneForBranch2; }
+
+ bool closedOrErrored() const { return flags() & Flag_ClosedOrErrored; }
+ void setClosedOrErrored() {
+ MOZ_ASSERT(!(flags() & Flag_ClosedOrErrored));
+ setFlags(flags() | Flag_ClosedOrErrored);
+ }
+
+ bool canceled1() const { return flags() & Flag_Canceled1; }
+ void setCanceled1(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled1));
+ setFlags(flags() | Flag_Canceled1);
+ setFixedSlot(Slot_Reason1, reason);
+ }
+
+ bool canceled2() const { return flags() & Flag_Canceled2; }
+ void setCanceled2(HandleValue reason) {
+ MOZ_ASSERT(!(flags() & Flag_Canceled2));
+ setFlags(flags() | Flag_Canceled2);
+ setFixedSlot(Slot_Reason2, reason);
+ }
+
+ Value reason1() const {
+ MOZ_ASSERT(canceled1());
+ return getFixedSlot(Slot_Reason1);
+ }
+
+ Value reason2() const {
+ MOZ_ASSERT(canceled2());
+ return getFixedSlot(Slot_Reason2);
+ }
+
+ PromiseObject* promise() {
+ return &getFixedSlot(Slot_Promise).toObject().as<PromiseObject>();
+ }
+ ReadableStream* stream() {
+ return &getFixedSlot(Slot_Stream).toObject().as<ReadableStream>();
+ }
+ ReadableStreamDefaultReader* reader() {
+ return &ReaderFromStream(stream())->as<ReadableStreamDefaultReader>();
+ }
+
+ ReadableStreamDefaultController* branch1() {
+ ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch1).toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
+ return controller;
+ }
+ void setBranch1(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch1);
+ setFixedSlot(Slot_Branch1, ObjectValue(*controller));
+ }
+
+ ReadableStreamDefaultController* branch2() {
+ ReadableStreamDefaultController* controller = &getFixedSlot(Slot_Branch2).toObject()
+ .as<ReadableStreamDefaultController>();
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
+ return controller;
+ }
+ void setBranch2(ReadableStreamDefaultController* controller) {
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_TeeBranch2);
+ setFixedSlot(Slot_Branch2, ObjectValue(*controller));
+ }
+
+ static TeeState* create(JSContext* cx, Handle<ReadableStream*> stream) {
+ Rooted<TeeState*> state(cx, NewObjectWithClassProto<TeeState>(cx));
+ if (!state)
+ return nullptr;
+
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ state->setFixedSlot(Slot_Flags, Int32Value(0));
+ state->setFixedSlot(Slot_Promise, ObjectValue(*promise));
+ state->setFixedSlot(Slot_Stream, ObjectValue(*stream));
+
+ return state;
+ }
+};
+
+const Class TeeState::class_ = {
+ "TeeState",
+ JSCLASS_HAS_RESERVED_SLOTS(SlotCount)
+};
+
+#define CLASS_SPEC(cls, nCtorArgs, nSlots, specFlags, classFlags, classOps) \
+const ClassSpec cls::classSpec_ = { \
+ GenericCreateConstructor<cls::constructor, nCtorArgs, gc::AllocKind::FUNCTION>, \
+ GenericCreatePrototype, \
+ nullptr, \
+ nullptr, \
+ cls##_methods, \
+ cls##_properties, \
+ nullptr, \
+ specFlags \
+}; \
+\
+const Class cls::class_ = { \
+ #cls, \
+ JSCLASS_HAS_RESERVED_SLOTS(nSlots) | \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls) | \
+ classFlags, \
+ classOps, \
+ &cls::classSpec_ \
+}; \
+\
+const Class cls::protoClass_ = { \
+ "object", \
+ JSCLASS_HAS_CACHED_PROTO(JSProto_##cls), \
+ JS_NULL_CLASS_OPS, \
+ &cls::classSpec_ \
+};
+
+// Streams spec, 3.2.3., steps 1-4.
+ReadableStream*
+ReadableStream::createStream(JSContext* cx, HandleObject proto /* = nullptr */)
+{
+ Rooted<ReadableStream*> stream(cx, NewObjectWithClassProto<ReadableStream>(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ // Step 1: Set this.[[state]] to "readable".
+ // Step 2: Set this.[[reader]] and this.[[storedError]] to undefined (implicit).
+ // Step 3: Set this.[[disturbed]] to false (implicit).
+ // Step 4: Set this.[[readableStreamController]] to undefined (implicit).
+ stream->setFixedSlot(StreamSlot_State, Int32Value(Readable));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableStreamDefaultController*
+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,
+ HandleObject proto /* = nullptr */)
+{
+ // Steps 1-4.
+ Rooted<ReadableStream*> stream(cx, createStream(cx));
+ if (!stream)
+ return nullptr;
+
+ // Step 8.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableStreamDefaultController,
+ // « this, underlyingSource, size,
+ // highWaterMark »).
+ RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream,
+ underlyingSource,
+ size,
+ highWaterMark));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingByteSource,
+ HandleValue highWaterMarkVal);
+
+// Streams spec, 3.2.3., steps 1-4, 7.
+ReadableStream*
+ReadableStream::createByteStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue highWaterMark, HandleObject proto /* = nullptr */)
+{
+ // Steps 1-4.
+ Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ // Step 7.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableByteStreamController,
+ // « this, underlyingSource, highWaterMark »).
+ RootedObject controller(cx, CreateReadableByteStreamController(cx, stream,
+ underlyingSource,
+ highWaterMark));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+
+ return stream;
+}
+
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ void* underlyingSource);
+
+ReadableStream*
+ReadableStream::createExternalSourceStream(JSContext* cx, void* underlyingSource,
+ uint8_t flags, HandleObject proto /* = nullptr */)
+{
+ Rooted<ReadableStream*> stream(cx, createStream(cx, proto));
+ if (!stream)
+ return nullptr;
+
+ RootedNativeObject controller(cx, CreateReadableByteStreamController(cx, stream,
+ underlyingSource));
+ if (!controller)
+ return nullptr;
+
+ stream->setFixedSlot(StreamSlot_Controller, ObjectValue(*controller));
+ AddControllerFlags(controller, flags << ControllerEmbeddingFlagsOffset);
+
+ return stream;
+}
+
+// Streams spec, 3.2.3.
+bool
+ReadableStream::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedValue val(cx, args.get(0));
+ RootedValue underlyingSource(cx, args.get(0));
+ RootedValue options(cx, args.get(1));
+
+ // Do argument handling first to keep the right order of error reporting.
+ if (underlyingSource.isUndefined()) {
+ RootedObject sourceObj(cx, NewBuiltinClassInstance<PlainObject>(cx));
+ if (!sourceObj)
+ return false;
+ underlyingSource = ObjectValue(*sourceObj);
+ }
+ RootedValue size(cx);
+ RootedValue highWaterMark(cx);
+
+ if (!options.isUndefined()) {
+ if (!GetProperty(cx, options, cx->names().size, &size))
+ return false;
+
+ if (!GetProperty(cx, options, cx->names().highWaterMark, &highWaterMark))
+ return false;
+ }
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStream"))
+ return false;
+
+ // Step 5: Let type be ? GetV(underlyingSource, "type").
+ RootedValue typeVal(cx);
+ if (!GetProperty(cx, underlyingSource, cx->names().type, &typeVal))
+ return false;
+
+ // Step 6: Let typeString be ? ToString(type).
+ RootedString type(cx, ToString<CanGC>(cx, typeVal));
+ if (!type)
+ return false;
+
+ int32_t notByteStream;
+ if (!CompareStrings(cx, type, cx->names().bytes, &notByteStream))
+ return false;
+
+ // Step 7.a & 8.a (reordered): If highWaterMark is undefined, let
+ // highWaterMark be 1 (or 0 for byte streams).
+ if (highWaterMark.isUndefined())
+ highWaterMark = Int32Value(notByteStream ? 1 : 0);
+
+ Rooted<ReadableStream*> stream(cx);
+
+ // Step 7: If typeString is "bytes",
+ if (!notByteStream) {
+ // Step 7.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableByteStreamController,
+ // « this, underlyingSource, highWaterMark »).
+ stream = createByteStream(cx, underlyingSource, highWaterMark);
+ } else if (typeVal.isUndefined()) {
+ // Step 8: Otherwise, if type is undefined,
+ // Step 8.b: Set this.[[readableStreamController]] to
+ // ? Construct(ReadableStreamDefaultController,
+ // « this, underlyingSource, size, highWaterMark »).
+ stream = createDefaultStream(cx, underlyingSource, size, highWaterMark);
+ } else {
+ // Step 9: Otherwise, throw a RangeError exception.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG);
+ return false;
+ }
+ if (!stream)
+ return false;
+
+ args.rval().setObject(*stream);
+ return true;
+}
+
+// Streams spec, 3.2.4.1. get locked
+static MOZ_MUST_USE bool
+ReadableStream_locked_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: Return ! IsReadableStreamLocked(this).
+ args.rval().setBoolean(stream->locked());
+ return true;
+}
+
+static bool
+ReadableStream_locked(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_locked_impl>(cx, args);
+}
+
+// Streams spec, 3.2.4.2. cancel ( reason )
+static MOZ_MUST_USE bool
+ReadableStream_cancel(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ // Step 1: If ! IsReadableStream(this) is false, return a promise rejected
+ // with a TypeError exception.
+ if (!Is<ReadableStream>(args.thisv())) {
+ ReportValueError3(cx, JSMSG_INCOMPATIBLE_PROTO, JSDVG_SEARCH_STACK, args.thisv(),
+ nullptr, "cancel", "");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: If ! IsReadableStreamLocked(this) is true, return a promise
+ // rejected with a TypeError exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_LOCKED, "cancel");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamCancel(this, reason).
+ RootedObject cancelPromise(cx, ReadableStream::cancel(cx, stream, args.get(0)));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+static MOZ_MUST_USE ReadableStreamDefaultReader*
+CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream);
+
+static MOZ_MUST_USE ReadableStreamBYOBReader*
+CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream);
+
+// Streams spec, 3.2.4.3. getReader()
+static MOZ_MUST_USE bool
+ReadableStream_getReader_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+ RootedObject reader(cx);
+
+ // Step 2: If mode is undefined, return
+ // ? AcquireReadableStreamDefaultReader(this).
+ RootedValue modeVal(cx);
+ HandleValue optionsVal = args.get(0);
+ if (!optionsVal.isUndefined()) {
+ if (!GetProperty(cx, optionsVal, cx->names().mode, &modeVal))
+ return false;
+ }
+
+ if (modeVal.isUndefined()) {
+ reader = CreateReadableStreamDefaultReader(cx, stream);
+ } else {
+ // Step 3: Set mode to ? ToString(mode) (implicit).
+ RootedString mode(cx, ToString<CanGC>(cx, modeVal));
+ if (!mode)
+ return false;
+
+ // Step 4: If mode is "byob", return ? AcquireReadableStreamBYOBReader(this).
+ int32_t notByob;
+ if (!CompareStrings(cx, mode, cx->names().byob, &notByob))
+ return false;
+ if (notByob) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_INVALID_READER_MODE);
+ // Step 5: Throw a RangeError exception.
+ return false;
+
+ }
+ reader = CreateReadableStreamBYOBReader(cx, stream);
+ }
+
+ // Reordered second part of steps 2 and 4.
+ if (!reader)
+ return false;
+ args.rval().setObject(*reader);
+ return true;
+}
+
+static bool
+ReadableStream_getReader(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_getReader_impl>(cx, args);
+}
+
+// Streams spec, 3.2.4.4. pipeThrough({ writable, readable }, options)
+static MOZ_MUST_USE bool
+ReadableStream_pipeThrough(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeThrough");
+ return false;
+ // // Step 1: Perform ? Invoke(this, "pipeTo", « writable, options »).
+
+ // // Step 2: Return readable.
+ // return readable;
+}
+
+// Streams spec, 3.2.4.5. pipeTo(dest, { preventClose, preventAbort, preventCancel } = {})
+// TODO: Unimplemented since spec is not complete yet.
+static MOZ_MUST_USE bool
+ReadableStream_pipeTo(JSContext* cx, unsigned argc, Value* vp)
+{
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, "pipeTo");
+ return false;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1, MutableHandle<ReadableStream*> branch2);
+
+// Streams spec, 3.2.4.6. tee()
+static MOZ_MUST_USE bool
+ReadableStream_tee_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStream*> stream(cx, &args.thisv().toObject().as<ReadableStream>());
+
+ // Step 2: Let branches be ? ReadableStreamTee(this, false).
+ Rooted<ReadableStream*> branch1(cx);
+ Rooted<ReadableStream*> branch2(cx);
+ if (!ReadableStreamTee(cx, stream, false, &branch1, &branch2))
+ return false;
+
+ // Step 3: Return ! CreateArrayFromList(branches).
+ RootedNativeObject branches(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!branches)
+ return false;
+ branches->setDenseInitializedLength(2);
+ branches->initDenseElement(0, ObjectValue(*branch1));
+ branches->initDenseElement(1, ObjectValue(*branch2));
+
+ args.rval().setObject(*branches);
+ return true;
+}
+
+static bool
+ReadableStream_tee(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStream(this) is false, throw a TypeError exception
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStream>, ReadableStream_tee_impl>(cx, args);
+}
+
+static const JSFunctionSpec ReadableStream_methods[] = {
+ JS_FN("cancel", ReadableStream_cancel, 1, 0),
+ JS_FN("getReader", ReadableStream_getReader, 0, 0),
+ JS_FN("pipeThrough", ReadableStream_pipeThrough, 2, 0),
+ JS_FN("pipeTo", ReadableStream_pipeTo, 1, 0),
+ JS_FN("tee", ReadableStream_tee, 0, 0),
+ JS_FS_END
+};
+
+static const JSPropertySpec ReadableStream_properties[] = {
+ JS_PSG("locked", ReadableStream_locked, 0),
+ JS_PS_END
+};
+
+CLASS_SPEC(ReadableStream, 0, StreamSlotCount, 0, 0, JS_NULL_CLASS_OPS);
+
+// Streams spec, 3.3.1. AcquireReadableStreamBYOBReader ( stream )
+// Always inlined.
+
+// Streams spec, 3.3.2. AcquireReadableStreamDefaultReader ( stream )
+// Always inlined.
+
+// Streams spec, 3.3.3. IsReadableStream ( x )
+// Using is<T> instead.
+
+// Streams spec, 3.3.4. IsReadableStreamDisturbed ( stream )
+// Using stream->disturbed() instead.
+
+// Streams spec, 3.3.5. IsReadableStreamLocked ( stream )
+bool
+ReadableStream::locked() const
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: If stream.[[reader]] is undefined, return false.
+ // Step 3: Return true.
+ // Special-casing for streams with external sources. Those can be locked
+ // explicitly via JSAPI, which is indicated by a controller flag.
+ // IsReadableStreamLocked is called from the controller's constructor, at
+ // which point we can't yet call ControllerFromStream(stream), but the
+ // source also can't be locked yet.
+ if (HasController(this) &&
+ (ControllerFlags(ControllerFromStream(this)) & ControllerFlag_SourceLocked))
+ {
+ return true;
+ }
+ return HasReader(this);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller);
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk);
+
+static bool
+TeeReaderReadHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee()));
+ HandleValue resultVal = args.get(0);
+
+ // Step a: Assert: Type(result) is Object.
+ RootedObject result(cx, &resultVal.toObject());
+
+ // Step b: Let value be ? Get(result, "value").
+ RootedValue value(cx);
+ if (!GetPropertyPure(cx, result, NameToId(cx->names().value), value.address()))
+ return false;
+
+ // Step c: Let done be ? Get(result, "done").
+ RootedValue doneVal(cx);
+ if (!GetPropertyPure(cx, result, NameToId(cx->names().done), doneVal.address()))
+ return false;
+
+ // Step d: Assert: Type(done) is Boolean.
+ bool done = doneVal.toBoolean();
+
+ // Step e: If done is true and teeState.[[closedOrErrored]] is false,
+ if (done && !teeState->closedOrErrored()) {
+ // Step i: If teeState.[[canceled1]] is false,
+ if (!teeState->canceled1()) {
+ // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
+ Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
+ if (!ReadableStreamDefaultControllerClose(cx, branch1))
+ return false;
+ }
+
+ // Step ii: If teeState.[[canceled2]] is false,
+ if (!teeState->canceled2()) {
+ // Step 1: Perform ! ReadableStreamDefaultControllerClose(branch1).
+ Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
+ if (!ReadableStreamDefaultControllerClose(cx, branch2))
+ return false;
+ }
+
+ // Step iii: Set teeState.[[closedOrErrored]] to true.
+ teeState->setClosedOrErrored();
+ }
+
+ // Step f: If teeState.[[closedOrErrored]] is true, return.
+ if (teeState->closedOrErrored())
+ return true;
+
+ // Step g: Let value1 and value2 be value.
+ RootedValue value1(cx, value);
+ RootedValue value2(cx, value);
+
+ // Step h: If teeState.[[canceled2]] is false and cloneForBranch2 is
+ // true, set value2 to
+ // ? StructuredDeserialize(StructuredSerialize(value2),
+ // the current Realm Record).
+ // TODO: add StructuredClone() intrinsic.
+ MOZ_ASSERT(!teeState->cloneForBranch2(), "tee(cloneForBranch2=true) should not be exposed");
+
+ // Step i: If teeState.[[canceled1]] is false, perform
+ // ? ReadableStreamDefaultControllerEnqueue(branch1, value1).
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ if (!teeState->canceled1()) {
+ controller = teeState->branch1();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value1))
+ return false;
+ }
+
+ // Step j: If teeState.[[canceled2]] is false,
+ // perform ? ReadableStreamDefaultControllerEnqueue(branch2, value2).
+ if (!teeState->canceled2()) {
+ controller = teeState->branch2();
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, value2))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamTee_Pull(JSContext* cx, Handle<TeeState*> teeState,
+ Handle<ReadableStream*> branchStream)
+{
+ // Step 1: Let reader be F.[[reader]], branch1 be F.[[branch1]],
+ // branch2 be F.[[branch2]], teeState be F.[[teeState]], and
+ // cloneForBranch2 be F.[[cloneForBranch2]].
+
+ // Step 2: Return the result of transforming
+ // ! ReadableStreamDefaultReaderRead(reader) by a fulfillment
+ // handler which takes the argument result and performs the
+ // following steps:
+ Rooted<ReadableStreamDefaultReader*> reader(cx, teeState->reader());
+ RootedObject readPromise(cx, ReadableStreamDefaultReader::read(cx, reader));
+ if (!readPromise)
+ return nullptr;
+
+ RootedObject onFulfilled(cx, NewHandler(cx, TeeReaderReadHandler, teeState));
+ if (!onFulfilled)
+ return nullptr;
+
+ return JS::CallOriginalPromiseThen(cx, readPromise, onFulfilled, nullptr);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamTee_Cancel(JSContext* cx, Handle<TeeState*> teeState,
+ Handle<ReadableStreamDefaultController*> branch, HandleValue reason)
+{
+ // Step 1: Let stream be F.[[stream]] and teeState be F.[[teeState]].
+ Rooted<ReadableStream*> stream(cx, teeState->stream());
+
+ bool bothBranchesCanceled = false;
+
+ // Step 2: Set teeState.[[canceled1]] to true.
+ // Step 3: Set teeState.[[reason1]] to reason.
+ if (ControllerFlags(branch) & ControllerFlag_TeeBranch1) {
+ teeState->setCanceled1(reason);
+ bothBranchesCanceled = teeState->canceled2();
+ } else {
+ MOZ_ASSERT(ControllerFlags(branch) & ControllerFlag_TeeBranch2);
+ teeState->setCanceled2(reason);
+ bothBranchesCanceled = teeState->canceled1();
+ }
+
+ // Step 4: If teeState.[[canceled1]] is true,
+ // Step 4: If teeState.[[canceled2]] is true,
+ if (bothBranchesCanceled) {
+ // Step a: Let compositeReason be
+ // ! CreateArrayFromList(« teeState.[[reason1]], teeState.[[reason2]] »).
+ RootedNativeObject compositeReason(cx, NewDenseFullyAllocatedArray(cx, 2));
+ if (!compositeReason)
+ return nullptr;
+
+ compositeReason->setDenseInitializedLength(2);
+ compositeReason->initDenseElement(0, teeState->reason1());
+ compositeReason->initDenseElement(1, teeState->reason2());
+ RootedValue compositeReasonVal(cx, ObjectValue(*compositeReason));
+
+ Rooted<PromiseObject*> promise(cx, teeState->promise());
+
+ // Step b: Let cancelResult be ! ReadableStreamCancel(stream, compositeReason).
+ RootedObject cancelResult(cx, ReadableStream::cancel(cx, stream, compositeReasonVal));
+ if (!cancelResult) {
+ if (!RejectPromiseWithPendingError(cx, promise))
+ return nullptr;
+ } else {
+ // Step c: Resolve teeState.[[promise]] with cancelResult.
+ RootedValue resultVal(cx, ObjectValue(*cancelResult));
+ if (!PromiseObject::resolve(cx, promise, resultVal))
+ return nullptr;
+ }
+ }
+
+ // Step 5: Return teeState.[[promise]].
+ return teeState->promise();
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e);
+
+// Streams spec, 3.3.6. step 21:
+// Upon rejection of reader.[[closedPromise]] with reason r,
+static bool
+TeeReaderClosedHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ Rooted<TeeState*> teeState(cx, TargetFromHandler<TeeState>(args.callee()));
+ HandleValue reason = args.get(0);
+
+ // Step a: If teeState.[[closedOrErrored]] is false, then:
+ if (!teeState->closedOrErrored()) {
+ // Step a.i: Perform ! ReadableStreamDefaultControllerError(pull.[[branch1]], r).
+ Rooted<ReadableStreamDefaultController*> branch1(cx, teeState->branch1());
+ if (!ReadableStreamControllerError(cx, branch1, reason))
+ return false;
+
+ // Step a.ii: Perform ! ReadableStreamDefaultControllerError(pull.[[branch2]], r).
+ Rooted<ReadableStreamDefaultController*> branch2(cx, teeState->branch2());
+ if (!ReadableStreamControllerError(cx, branch2, reason))
+ return false;
+
+ // Step a.iii: Set teeState.[[closedOrErrored]] to true.
+ teeState->setClosedOrErrored();
+ }
+
+ return true;
+}
+
+// Streams spec, 3.3.6. ReadableStreamTee ( stream, cloneForBranch2 )
+static MOZ_MUST_USE bool
+ReadableStreamTee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+ // Step 2: Assert: Type(cloneForBranch2) is Boolean (implicit).
+
+ // Step 3: Let reader be ? AcquireReadableStreamDefaultReader(stream).
+ Rooted<ReadableStreamDefaultReader*> reader(cx, CreateReadableStreamDefaultReader(cx, stream));
+ if (!reader)
+ return false;
+
+ // Step 4: Let teeState be Record {[[closedOrErrored]]: false,
+ // [[canceled1]]: false,
+ // [[canceled2]]: false,
+ // [[reason1]]: undefined,
+ // [[reason2]]: undefined,
+ // [[promise]]: a new promise}.
+ Rooted<TeeState*> teeState(cx, TeeState::create(cx, stream));
+ if (!teeState)
+ return false;
+
+ // Steps 5-10 omitted because our implementation works differently.
+
+ // Step 5: Let pull be a new ReadableStreamTee pull function.
+ // Step 6: Set pull.[[reader]] to reader, pull.[[teeState]] to teeState, and
+ // pull.[[cloneForBranch2]] to cloneForBranch2.
+ // Step 7: Let cancel1 be a new ReadableStreamTee branch 1 cancel function.
+ // Step 8: Set cancel1.[[stream]] to stream and cancel1.[[teeState]] to
+ // teeState.
+
+ // Step 9: Let cancel2 be a new ReadableStreamTee branch 2 cancel function.
+ // Step 10: Set cancel2.[[stream]] to stream and cancel2.[[teeState]] to
+ // teeState.
+
+ // Step 11: Let underlyingSource1 be ! ObjectCreate(%ObjectPrototype%).
+ // Step 12: Perform ! CreateDataProperty(underlyingSource1, "pull", pull).
+ // Step 13: Perform ! CreateDataProperty(underlyingSource1, "cancel", cancel1).
+
+ // Step 14: Let branch1Stream be ! Construct(ReadableStream, underlyingSource1).
+ RootedValue hwmValue(cx, NumberValue(1));
+ RootedValue underlyingSource(cx, ObjectValue(*teeState));
+ branch1Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource,
+ UndefinedHandleValue,
+ hwmValue));
+ if (!branch1Stream)
+ return false;
+
+ Rooted<ReadableStreamDefaultController*> branch1(cx);
+ branch1 = &ControllerFromStream(branch1Stream)->as<ReadableStreamDefaultController>();
+ AddControllerFlags(branch1, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch1);
+ teeState->setBranch1(branch1);
+
+ // Step 15: Let underlyingSource2 be ! ObjectCreate(%ObjectPrototype%).
+ // Step 16: Perform ! CreateDataProperty(underlyingSource2, "pull", pull).
+ // Step 17: Perform ! CreateDataProperty(underlyingSource2, "cancel", cancel2).
+
+ // Step 18: Let branch2Stream be ! Construct(ReadableStream, underlyingSource2).
+ branch2Stream.set(ReadableStream::createDefaultStream(cx, underlyingSource,
+ UndefinedHandleValue,
+ hwmValue));
+ if (!branch2Stream)
+ return false;
+
+ Rooted<ReadableStreamDefaultController*> branch2(cx);
+ branch2 = &ControllerFromStream(branch2Stream)->as<ReadableStreamDefaultController>();
+ AddControllerFlags(branch2, ControllerFlag_TeeBranch | ControllerFlag_TeeBranch2);
+ teeState->setBranch2(branch2);
+
+ // Step 19: Set pull.[[branch1]] to branch1Stream.[[readableStreamController]].
+ // Step 20: Set pull.[[branch2]] to branch2Stream.[[readableStreamController]].
+
+ // Step 21: Upon rejection of reader.[[closedPromise]] with reason r,
+ RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
+
+ RootedObject onRejected(cx, NewHandler(cx, TeeReaderClosedHandler, teeState));
+ if (!onRejected)
+ return false;
+
+ if (!JS::AddPromiseReactions(cx, closedPromise, nullptr, onRejected))
+ return false;
+
+ // Step 22: Return « branch1, branch2 ».
+ return true;
+}
+
+// Streams spec, 3.4.1. ReadableStreamAddReadIntoRequest ( stream )
+static MOZ_MUST_USE PromiseObject*
+ReadableStreamAddReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 1: MOZ_ASSERT: ! IsReadableStreamBYOBReader(stream.[[reader]]) is true.
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+ RootedNativeObject reader(cx, &val.toObject().as<ReadableStreamBYOBReader>());
+
+ // Step 2: MOZ_ASSERT: stream.[[state]] is "readable" or "closed".
+ MOZ_ASSERT(stream->readable() || stream->closed());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ // Step 4: Let readIntoRequest be Record {[[promise]]: promise}.
+ // Step 5: Append readIntoRequest as the last element of stream.[[reader]].[[readIntoRequests]].
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
+ // Since [[promise]] is the Record's only field, we store it directly.
+ val = ObjectValue(*promise);
+ if (!AppendToList(cx, readIntoRequests, val))
+ return nullptr;
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+// Streams spec, 3.4.2. ReadableStreamAddReadRequest ( stream )
+static MOZ_MUST_USE PromiseObject*
+ReadableStreamAddReadRequest(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ MOZ_ASSERT(stream->is<ReadableStream>());
+
+ // Step 1: Assert: ! IsReadableStreamDefaultReader(stream.[[reader]]) is true.
+ RootedNativeObject reader(cx, ReaderFromStream(stream));
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 3: Let promise be a new promise.
+ Rooted<PromiseObject*> promise(cx, PromiseObject::createSkippingExecutor(cx));
+ if (!promise)
+ return nullptr;
+
+ // Step 4: Let readRequest be Record {[[promise]]: promise}.
+ // Step 5: Append readRequest as the last element of stream.[[reader]].[[readRequests]].
+ RootedValue val(cx, reader->getFixedSlot(ReaderSlot_Requests));
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+
+ // Since [[promise]] is the Record's only field, we store it directly.
+ val = ObjectValue(*promise);
+ if (!AppendToList(cx, readRequests, val))
+ return nullptr;
+
+ // Step 6: Return promise.
+ return promise;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerCancelSteps(JSContext* cx,
+ HandleNativeObject controller, HandleValue reason);
+
+// Used for transforming the result of promise fulfillment/rejection.
+static bool
+ReturnUndefined(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setUndefined();
+ return true;
+}
+
+MOZ_MUST_USE bool
+ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream);
+
+// Streams spec, 3.4.3. ReadableStreamCancel ( stream, reason )
+/* static */ MOZ_MUST_USE JSObject*
+ReadableStream::cancel(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
+{
+ // Step 1: Set stream.[[disturbed]] to true.
+ uint32_t state = StreamState(stream) | ReadableStream::Disturbed;
+ SetStreamState(stream, state);
+
+ // Step 2: If stream.[[state]] is "closed", return a new promise resolved
+ // with undefined.
+ if (stream->closed())
+ return PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
+
+ // Step 3: If stream.[[state]] is "errored", return a new promise rejected
+ // with stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 4: Perform ! ReadableStreamClose(stream).
+ if (!ReadableStreamCloseInternal(cx, stream))
+ return nullptr;
+
+ // Step 5: Let sourceCancelPromise be
+ // ! stream.[[readableStreamController]].[[CancelSteps]](reason).
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ RootedObject sourceCancelPromise(cx);
+ sourceCancelPromise = ReadableStreamControllerCancelSteps(cx, controller, reason);
+ if (!sourceCancelPromise)
+ return nullptr;
+
+ // Step 6: Return the result of transforming sourceCancelPromise by a
+ // fulfillment handler that returns undefined.
+ RootedAtom funName(cx, cx->names().empty);
+ RootedFunction returnUndefined(cx, NewNativeFunction(cx, ReturnUndefined, 0, funName));
+ if (!returnUndefined)
+ return nullptr;
+ return JS::CallOriginalPromiseThen(cx, sourceCancelPromise, returnUndefined, nullptr);
+}
+
+// Streams spec, 3.4.4. ReadableStreamClose ( stream )
+MOZ_MUST_USE bool
+ReadableStreamCloseInternal(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 1: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ uint32_t state = StreamState(stream);
+ // Step 2: Set stream.[[state]] to "closed".
+ SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Closed);
+
+ // Step 3: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+
+ // Step 4: If reader is undefined, return.
+ if (val.isUndefined())
+ return true;
+
+ // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+ if (reader->is<ReadableStreamDefaultReader>()) {
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+ uint32_t len = readRequests->getDenseInitializedLength();
+ RootedObject readRequest(cx);
+ RootedObject resultObj(cx);
+ RootedValue resultVal(cx);
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Resolve readRequest.[[promise]] with
+ // ! CreateIterResultObject(undefined, true).
+ readRequest = &readRequests->getDenseElement(i).toObject();
+ resultObj = CreateIterResultObject(cx, UndefinedHandleValue, true);
+ if (!resultObj)
+ return false;
+ resultVal = ObjectValue(*resultObj);
+ if (!ResolvePromise(cx, readRequest, resultVal))
+ return false;
+ }
+
+ // Step b: Set reader.[[readRequests]] to an empty List.
+ reader->setFixedSlot(ReaderSlot_Requests, UndefinedValue());
+ }
+ }
+
+ // Step 6: Resolve reader.[[closedPromise]] with undefined.
+ // Step 7: Return (implicit).
+ RootedObject closedPromise(cx, &reader->getFixedSlot(ReaderSlot_ClosedPromise).toObject());
+ if (!ResolvePromise(cx, closedPromise, UndefinedHandleValue))
+ return false;
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
+ cx->runtime()->readableStreamClosedCallback)
+ {
+ NativeObject* controller = ControllerFromStream(stream);
+ void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ cx->runtime()->readableStreamClosedCallback(cx, stream, source, stream->embeddingFlags());
+ }
+
+ return true;
+}
+
+// Streams spec, 3.4.5. ReadableStreamError ( stream, e )
+MOZ_MUST_USE bool
+ReadableStreamErrorInternal(JSContext* cx, Handle<ReadableStream*> stream, HandleValue e)
+{
+ // Step 1: Assert: ! IsReadableStream(stream) is true (implicit).
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 3: Set stream.[[state]] to "errored".
+ uint32_t state = StreamState(stream);
+ SetStreamState(stream, (state & ReadableStream::Disturbed) | ReadableStream::Errored);
+
+ // Step 4: Set stream.[[storedError]] to e.
+ stream->setFixedSlot(StreamSlot_StoredError, e);
+
+ // Step 5: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+
+ // Step 6: If reader is undefined, return.
+ if (val.isUndefined())
+ return true;
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+
+ // Steps 7,8: (Identical in our implementation.)
+ // Step a: Repeat for each readRequest that is an element of
+ // reader.[[readRequests]],
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readRequests(cx, &val.toObject().as<NativeObject>());
+ Rooted<PromiseObject*> readRequest(cx);
+ uint32_t len = readRequests->getDenseInitializedLength();
+ for (uint32_t i = 0; i < len; i++) {
+ // Step i: Reject readRequest.[[promise]] with e.
+ val = readRequests->getDenseElement(i);
+ readRequest = &val.toObject().as<PromiseObject>();
+ if (!PromiseObject::reject(cx, readRequest, e))
+ return false;
+ }
+
+ // Step b: Set reader.[[readRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return false;
+
+ // Step 9: Reject reader.[[closedPromise]] with e.
+ val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
+ Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
+ if (!PromiseObject::reject(cx, closedPromise, e))
+ return false;
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource &&
+ cx->runtime()->readableStreamErroredCallback)
+ {
+ NativeObject* controller = ControllerFromStream(stream);
+ void* source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ cx->runtime()->readableStreamErroredCallback(cx, stream, source,
+ stream->embeddingFlags(), e);
+ }
+
+ return true;
+}
+
+// Streams spec, 3.4.6. ReadableStreamFulfillReadIntoRequest( stream, chunk, done )
+// Streams spec, 3.4.7. ReadableStreamFulfillReadRequest ( stream, chunk, done )
+// These two spec functions are identical in our implementation.
+static MOZ_MUST_USE bool
+ReadableStreamFulfillReadOrReadIntoRequest(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue chunk, bool done)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ RootedValue val(cx, stream->getFixedSlot(StreamSlot_Reader));
+ RootedNativeObject reader(cx, &val.toObject().as<NativeObject>());
+
+ // Step 2: Let readIntoRequest be the first element of
+ // reader.[[readIntoRequests]].
+ // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
+ // all other elements downward (so that the second becomes the first,
+ // and so on).
+ val = reader->getFixedSlot(ReaderSlot_Requests);
+ RootedNativeObject readIntoRequests(cx, &val.toObject().as<NativeObject>());
+ Rooted<PromiseObject*> readIntoRequest(cx);
+ readIntoRequest = ShiftFromList<PromiseObject>(cx, readIntoRequests);
+ MOZ_ASSERT(readIntoRequest);
+
+ // Step 4: Resolve readIntoRequest.[[promise]] with
+ // ! CreateIterResultObject(chunk, done).
+ RootedObject iterResult(cx, CreateIterResultObject(cx, chunk, done));
+ if (!iterResult)
+ return false;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::resolve(cx, readIntoRequest, val);
+}
+
+// Streams spec, 3.4.8. ReadableStreamGetNumReadIntoRequests ( stream )
+// Streams spec, 3.4.9. ReadableStreamGetNumReadRequests ( stream )
+// (Identical implementation.)
+static uint32_t
+ReadableStreamGetNumReadRequests(ReadableStream* stream)
+{
+ // Step 1: Return the number of elements in
+ // stream.[[reader]].[[readRequests]].
+ if (!HasReader(stream))
+ return 0;
+ NativeObject* reader = ReaderFromStream(stream);
+ Value readRequests = reader->getFixedSlot(ReaderSlot_Requests);
+ return readRequests.toObject().as<NativeObject>().getDenseInitializedLength();
+}
+
+// Stream spec 3.4.10. ReadableStreamHasBYOBReader ( stream )
+static MOZ_MUST_USE bool
+ReadableStreamHasBYOBReader(ReadableStream* stream)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ // Step 3: If ! IsReadableStreamBYOBReader(reader) is false, return false.
+ // Step 4: Return true.
+ Value reader = stream->getFixedSlot(StreamSlot_Reader);
+ return reader.isObject() && reader.toObject().is<ReadableStreamBYOBReader>();
+}
+
+// Streap spec 3.4.11. ReadableStreamHasDefaultReader ( stream )
+static MOZ_MUST_USE bool
+ReadableStreamHasDefaultReader(ReadableStream* stream)
+{
+ // Step 1: Let reader be stream.[[reader]].
+ // Step 2: If reader is undefined, return false.
+ // Step 3: If ! ReadableStreamDefaultReader(reader) is false, return false.
+ // Step 4: Return true.
+ Value reader = stream->getFixedSlot(StreamSlot_Reader);
+ return reader.isObject() && reader.toObject().is<ReadableStreamDefaultReader>();
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericInitialize(JSContext* cx,
+ HandleNativeObject reader,
+ Handle<ReadableStream*> stream);
+
+// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
+// Steps 2-4.
+static MOZ_MUST_USE ReadableStreamDefaultReader*
+CreateReadableStreamDefaultReader(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = NewBuiltinClassInstance<ReadableStreamDefaultReader>(cx);
+ if (!reader)
+ return nullptr;
+
+ // Step 2: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_LOCKED);
+ return nullptr;
+ }
+
+ // Step 3: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
+ return nullptr;
+
+ // Step 4: Set this.[[readRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return nullptr;
+
+ return reader;
+}
+
+// Stream spec, 3.5.3. new ReadableStreamDefaultReader ( stream )
+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 (!Is<ReadableStream>(args.get(0))) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultReader", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());
+
+ RootedObject reader(cx, CreateReadableStreamDefaultReader(cx, stream));
+ if (!reader)
+ return false;
+
+ args.rval().setObject(*reader);
+ return true;
+}
+
+// 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.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "get closed");
+
+ // Step 2: Return this.[[closedPromise]].
+ NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue 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.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "cancel");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (!ReaderHasStream(reader)) {
+ 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));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+// 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.
+ if (!Is<ReadableStreamDefaultReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamDefaultReader", "read");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: Return ! ReadableStreamDefaultReaderRead(this).
+ JSObject* readPromise = ReadableStreamDefaultReader::read(cx, reader);
+ if (!readPromise)
+ return false;
+ args.rval().setObject(*readPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);
+
+// Streams spec, 3.5.4.4. releaseLock ( )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamDefaultReader>();
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return.
+ if (!ReaderHasStream(reader)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
+ Value val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ NativeObject* readRequests = &val.toObject().as<NativeObject>();
+ uint32_t len = readRequests->getDenseInitializedLength();
+ if (len != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_EMPTY,
+ "releaseLock");
+ return false;
+ }
+ }
+
+ // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+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);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultReader>,
+ ReadableStreamDefaultReader_releaseLock_impl>(cx, args);
+}
+
+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
+};
+
+static const JSPropertySpec ReadableStreamDefaultReader_properties[] = {
+ JS_PSG("closed", ReadableStreamDefaultReader_closed, 0),
+ JS_PS_END
+};
+
+CLASS_SPEC(ReadableStreamDefaultReader, 1, ReaderSlotCount, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
+
+
+// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
+// Steps 2-5.
+static MOZ_MUST_USE ReadableStreamBYOBReader*
+CreateReadableStreamBYOBReader(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ // Step 2: If ! IsReadableByteStreamController(stream.[[readableStreamController]])
+ // is false, throw a TypeError exception.
+ if (!ControllerFromStream(stream)->is<ReadableByteStreamController>()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
+ "ReadableStream.getReader('byob')");
+ return nullptr;
+ }
+
+ // Step 3: If ! IsReadableStreamLocked(stream) is true, throw a TypeError
+ // exception.
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
+ return nullptr;
+ }
+
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = NewBuiltinClassInstance<ReadableStreamBYOBReader>(cx);
+ if (!reader)
+ return nullptr;
+
+ // Step 4: Perform ! ReadableStreamReaderGenericInitialize(this, stream).
+ if (!ReadableStreamReaderGenericInitialize(cx, reader, stream))
+ return nullptr;
+
+ // Step 5: Set this.[[readIntoRequests]] to a new empty List.
+ if (!SetNewList(cx, reader, ReaderSlot_Requests))
+ return nullptr;
+
+ return reader;
+}
+
+// Streams spec, 3.6.3 new ReadableStreamBYOBReader ( stream )
+bool
+ReadableStreamBYOBReader::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBReader"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ if (!Is<ReadableStream>(args.get(0))) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBReader", "ReadableStream", args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &args.get(0).toObject().as<ReadableStream>());
+ RootedObject reader(cx, CreateReadableStreamBYOBReader(cx, stream));
+ if (!reader)
+ return false;
+
+ args.rval().setObject(*reader);
+ return true;
+}
+
+// Streams spec, 3.6.4.1 get closed
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_closed(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "get closed");
+
+ // Step 2: Return this.[[closedPromise]].
+ NativeObject* reader = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(reader->getFixedSlot(ReaderSlot_ClosedPromise));
+ return true;
+}
+
+// Streams spec, 3.6.4.2. cancel ( reason )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_cancel(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "cancel");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ RootedNativeObject reader(cx, &args.thisv().toObject().as<NativeObject>());
+ if (!ReaderHasStream(reader)) {
+ 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));
+ if (!cancelPromise)
+ return false;
+ args.rval().setObject(*cancelPromise);
+ return true;
+}
+
+// Streams spec, 3.6.4.3 read ( )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_read(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue viewVal = args.get(0);
+
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false, return a promise
+ // rejected with a TypeError exception.
+ if (!Is<ReadableStreamBYOBReader>(args.thisv()))
+ return RejectNonGenericMethod(cx, args, "ReadableStreamBYOBReader", "read");
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return a promise
+ // rejected with a TypeError exception.
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
+ if (!ReaderHasStream(reader)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_OWNED, "read");
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 3: If Type(view) is not Object, return a promise rejected with a
+ // TypeError exception.
+ // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot,
+ // return a promise rejected with a TypeError exception.
+ if (!Is<ArrayBufferViewObject>(viewVal)) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBReader.read", "Typed Array", viewVal);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ Rooted<ArrayBufferViewObject*> view(cx, &viewVal.toObject().as<ArrayBufferViewObject>());
+
+ // Step 5: If view.[[ByteLength]] is 0, return a promise rejected with a
+ // TypeError exception.
+ // Note: It's ok to use the length in number of elements here because all we
+ // want to know is whether it's < 0.
+ if (JS_GetArrayBufferViewByteLength(view) == 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW);
+ return ReturnPromiseRejectedWithPendingError(cx, args);
+ }
+
+ // Step 6: Return ! ReadableStreamBYOBReaderRead(this, view).
+ JSObject* readPromise = ReadableStreamBYOBReader::read(cx, reader, view);
+ if (!readPromise)
+ return false;
+ args.rval().setObject(*readPromise);
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader);
+
+// Streams spec, 3.6.4.4. releaseLock ( )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBReader_releaseLock_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBReader*> reader(cx);
+ reader = &args.thisv().toObject().as<ReadableStreamBYOBReader>();
+
+ // Step 2: If this.[[ownerReadableStream]] is undefined, return.
+ if (!ReaderHasStream(reader)) {
+ args.rval().setUndefined();
+ return true;
+ }
+
+ // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
+ Value val = reader->getFixedSlot(ReaderSlot_Requests);
+ if (!val.isUndefined()) {
+ NativeObject* readRequests = &val.toObject().as<NativeObject>();
+ uint32_t len = readRequests->getDenseInitializedLength();
+ if (len != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMREADER_NOT_EMPTY, "releaseLock");
+ return false;
+ }
+ }
+
+ // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+static bool
+ReadableStreamBYOBReader_releaseLock(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBReader(this) is false,
+ // throw a TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBReader>,
+ ReadableStreamBYOBReader_releaseLock_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamBYOBReader_properties[] = {
+ JS_PSG("closed", ReadableStreamBYOBReader_closed, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamBYOBReader_methods[] = {
+ JS_FN("cancel", ReadableStreamBYOBReader_cancel, 1, 0),
+ JS_FN("read", ReadableStreamBYOBReader_read, 1, 0),
+ JS_FN("releaseLock", ReadableStreamBYOBReader_releaseLock, 0, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamBYOBReader, 1, 3, ClassSpec::DontDefineConstructor, 0, JS_NULL_CLASS_OPS);
+
+inline static MOZ_MUST_USE bool
+ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.7.1. IsReadableStreamDefaultReader ( x )
+// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamDefaultReader>()
+
+// Streams spec, 3.7.2. IsReadableStreamBYOBReader ( x )
+// Implemented via intrinsic_isInstanceOfBuiltin<ReadableStreamBYOBReader>()
+
+// Streams spec, 3.7.3. ReadableStreamReaderGenericCancel ( reader, reason )
+static MOZ_MUST_USE JSObject*
+ReadableStreamReaderGenericCancel(JSContext* cx, HandleNativeObject reader, HandleValue reason)
+{
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 2: Assert: stream is not undefined (implicit).
+
+ // Step 3: Return ! ReadableStreamCancel(stream, reason).
+ return &ReadableStreamCancel(cx, stream, reason)->as<PromiseObject>();
+}
+
+// Streams spec, 3.7.4. ReadableStreamReaderGenericInitialize ( reader, stream )
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericInitialize(JSContext* cx, HandleNativeObject reader,
+ Handle<ReadableStream*> stream)
+{
+ // Step 1: Set reader.[[ownerReadableStream]] to stream.
+ reader->setFixedSlot(ReaderSlot_Stream, ObjectValue(*stream));
+
+ // Step 2: Set stream.[[reader]] to reader.
+ stream->setFixedSlot(StreamSlot_Reader, ObjectValue(*reader));
+
+ // Step 3: If stream.[[state]] is "readable",
+ RootedObject promise(cx);
+ if (stream->readable()) {
+ // Step a: Set reader.[[closedPromise]] to a new promise.
+ promise = PromiseObject::createSkippingExecutor(cx);
+ } else if (stream->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());
+
+ // Step ii: Set reader.[[closedPromise]] to a new promise rejected with
+ // stream.[[storedError]].
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ promise = PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ if (!promise)
+ return false;
+
+ reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*promise));
+ return true;
+}
+
+// Streams spec, 3.7.5. ReadableStreamReaderGenericRelease ( reader )
+static MOZ_MUST_USE bool
+ReadableStreamReaderGenericRelease(JSContext* cx, HandleNativeObject reader)
+{
+ // Step 1: Assert: reader.[[ownerReadableStream]] is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 2: Assert: reader.[[ownerReadableStream]].[[reader]] is reader.
+ MOZ_ASSERT(&stream->getFixedSlot(StreamSlot_Reader).toObject() == reader);
+
+ // 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);
+ // Not much we can do about uncatchable exceptions, just bail.
+ if (!GetAndClearException(cx, &exn))
+ return false;
+
+ // Step 3: If reader.[[ownerReadableStream]].[[state]] is "readable", reject
+ // reader.[[closedPromise]] with a TypeError exception.
+ if (stream->readable()) {
+ Value val = reader->getFixedSlot(ReaderSlot_ClosedPromise);
+ Rooted<PromiseObject*> closedPromise(cx, &val.toObject().as<PromiseObject>());
+ if (!PromiseObject::reject(cx, closedPromise, exn))
+ return false;
+ } 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;
+ reader->setFixedSlot(ReaderSlot_ClosedPromise, ObjectValue(*closedPromise));
+ }
+
+ // Step 5: Set reader.[[ownerReadableStream]].[[reader]] to undefined.
+ stream->setFixedSlot(StreamSlot_Reader, UndefinedValue());
+
+ // Step 6: Set reader.[[ownerReadableStream]] to undefined.
+ reader->setFixedSlot(ReaderSlot_Stream, UndefinedValue());
+
+ return true;
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerPullInto(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<ArrayBufferViewObject*> view);
+
+// Streams spec, 3.7.6. ReadableStreamBYOBReaderRead ( reader, view )
+/* static */ MOZ_MUST_USE JSObject*
+ReadableStreamBYOBReader::read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
+ Handle<ArrayBufferViewObject*> view)
+{
+ MOZ_ASSERT(reader->is<ReadableStreamBYOBReader>());
+
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 3: Set stream.[[disturbed]] to true.
+ SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);
+
+ // Step 4: If stream.[[state]] is "errored", return a promise rejected with
+ // stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 5: Return ! ReadableByteStreamControllerPullInto(stream.[[readableStreamController]], view).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+ return ReadableByteStreamControllerPullInto(cx, controller, view);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.7.7. ReadableStreamDefaultReaderRead ( reader )
+MOZ_MUST_USE JSObject*
+ReadableStreamDefaultReader::read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader)
+{
+ // Step 1: Let stream be reader.[[ownerReadableStream]].
+ // Step 2: Assert: stream is not undefined.
+ Rooted<ReadableStream*> stream(cx, StreamFromReader(reader));
+
+ // Step 3: Set stream.[[disturbed]] to true.
+ SetStreamState(stream, StreamState(stream) | ReadableStream::Disturbed);
+
+ // Step 4: If stream.[[state]] is "closed", return a new promise resolved with
+ // ! CreateIterResultObject(undefined, true).
+ if (stream->closed()) {
+ RootedObject iterResult(cx, CreateIterResultObject(cx, UndefinedHandleValue, true));
+ if (!iterResult)
+ return nullptr;
+ RootedValue iterResultVal(cx, ObjectValue(*iterResult));
+ return PromiseObject::unforgeableResolve(cx, iterResultVal);
+ }
+
+ // Step 5: If stream.[[state]] is "errored", return a new promise rejected with
+ // stream.[[storedError]].
+ if (stream->errored()) {
+ RootedValue storedError(cx, stream->getFixedSlot(StreamSlot_StoredError));
+ return PromiseObject::unforgeableReject(cx, storedError);
+ }
+
+ // Step 6: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 7: Return ! stream.[[readableStreamController]].[[PullSteps]]().
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ return ReadableStreamControllerPullSteps(cx, controller);
+}
+
+// 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);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+
+ // Step i: Set controller.[[started]] to true.
+ AddControllerFlags(controller, ControllerFlag_Started);
+
+ // Step ii: Assert: controller.[[pulling]] is false.
+ // Step iii: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) &
+ (ControllerFlag_Pulling | ControllerFlag_PullAgain)));
+
+ // Step iv: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
+ // or
+ // Step iv: Perform ! ReadableByteStreamControllerCallPullIfNeeded((controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue e);
+
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e);
+
+// 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);
+ RootedNativeObject controllerObj(cx, TargetFromHandler<NativeObject>(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));
+ }
+
+ // 3.10.3, Step 16.b.i: If stream.[[state]] is "readable", perform
+ // ! ReadableByteStreamControllerError(controller, r).
+ if (StreamFromController(controllerObj)->readable())
+ return ReadableStreamControllerError(cx, controllerObj, args.get(0));
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ValidateAndNormalizeHighWaterMark(JSContext* cx,
+ HandleValue highWaterMarkVal,
+ double* highWaterMark);
+
+static MOZ_MUST_USE bool
+ValidateAndNormalizeQueuingStrategy(JSContext* cx,
+ HandleValue size,
+ HandleValue highWaterMarkVal,
+ double* highWaterMark);
+
+// Streams spec, 3.8.3 new ReadableStreamDefaultController ( stream, underlyingSource,
+// size, highWaterMark )
+// Steps 3 - 11.
+static MOZ_MUST_USE ReadableStreamDefaultController*
+CreateReadableStreamDefaultController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingSource, HandleValue size,
+ HandleValue highWaterMarkVal)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableStreamDefaultController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingSource]] to underlyingSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingSource);
+
+ // Step 5: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 6: Set this.[[started]], this.[[closeRequested]], this.[[pullAgain]],
+ // and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));
+
+ // Step 7: Let normalizedStrategy be
+ // ? ValidateAndNormalizeQueuingStrategy(size, highWaterMark).
+ double highWaterMark;
+ if (!ValidateAndNormalizeQueuingStrategy(cx, size, highWaterMarkVal, &highWaterMark))
+ return nullptr;
+
+ // Step 8: Set this.[[strategySize]] to normalizedStrategy.[[size]] and
+ // this.[[strategyHWM]] to normalizedStrategy.[[highWaterMark]].
+ controller->setFixedSlot(DefaultControllerSlot_StrategySize, size);
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));
+
+ // Step 9: Let controller be this (implicit).
+
+ // Step 10: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ RootedValue startResult(cx);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingSource, cx->names().start, controllerVal, &startResult))
+ return nullptr;
+
+ // Step 11: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+// Streams spec, 3.8.3.
+// new ReadableStreamDefaultController( stream, underlyingSource, size,
+// highWaterMark )
+bool
+ReadableStreamDefaultController::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamDefaultController"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ HandleValue streamVal = args.get(0);
+ if (!Is<ReadableStream>(streamVal)) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());
+
+ // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
+ // TypeError exception.
+ if (HasController(stream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_CONTROLLER_SET);
+ return false;
+ }
+
+ // Steps 3-11.
+ RootedObject controller(cx, CreateReadableStreamDefaultController(cx, stream, args.get(1),
+ args.get(2), args.get(3)));
+ if (!controller)
+ return false;
+
+ args.rval().setObject(*controller);
+ return true;
+}
+
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);
+
+// Streams spec, 3.8.4.1. get desiredSize
+// and
+// Streams spec, 3.10.4.2. get desiredSize
+static MOZ_MUST_USE bool
+ReadableStreamController_desiredSize_impl(JSContext* cx, const CallArgs& args)
+{
+ RootedNativeObject controller(cx);
+ controller = &args.thisv().toObject().as<NativeObject>();
+
+ // Streams spec, 3.9.8. steps 1-4.
+ // 3.9.8. Step 1: Let stream be controller.[[controlledReadableStream]].
+ ReadableStream* stream = StreamFromController(controller);
+
+ // 3.9.8. Step 2: Let state be stream.[[state]].
+ // 3.9.8. Step 3: If state is "errored", return null.
+ if (stream->errored()) {
+ args.rval().setNull();
+ return true;
+ }
+
+ // 3.9.8. Step 4: If state is "closed", return 0.
+ if (stream->closed()) {
+ args.rval().setInt32(0);
+ return true;
+ }
+
+ // Step 2: Return ! ReadableStreamDefaultControllerGetDesiredSize(this).
+ args.rval().setNumber(ReadableStreamControllerGetDesiredSizeUnchecked(controller));
+ return true;
+}
+
+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);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamController_desiredSize_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller);
+
+// Unified implementation of steps 2-3 of 3.8.4.2 and 3.10.4.3.
+static MOZ_MUST_USE bool
+VerifyControllerStateForClosing(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ ReadableStream* stream = StreamFromController(controller);
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ return false;
+ }
+
+ return true;
+}
+
+// Streams spec, 3.8.4.2 close()
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_close_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Steps 2-3.
+ if (!VerifyControllerStateForClosing(cx, controller))
+ return false;
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerClose(this).
+ if (!ReadableStreamDefaultControllerClose(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_close(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_close_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk);
+
+// Streams spec, 3.8.4.3. enqueue ( chunk )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_enqueue_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "close");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ ReadableStream* stream = StreamFromController(controller);
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "close");
+ return false;
+ }
+
+ // Step 4: Return ! ReadableStreamDefaultControllerEnqueue(this, chunk).
+ if (!ReadableStreamDefaultControllerEnqueue(cx, controller, args.get(0)))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_enqueue(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_enqueue_impl>(cx, args);
+}
+
+// Streams spec, 3.8.4.4. error ( e )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultController_error_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableStreamDefaultController>();
+
+ // Step 2: Let stream be this.[[controlledReadableStream]].
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
+ if (!ReadableStreamControllerError(cx, controller, args.get(0)))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamDefaultController_error(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamDefaultController(this) is false, throw a
+ // TypeError exception.
+
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamDefaultController>,
+ ReadableStreamDefaultController_error_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamDefaultController_properties[] = {
+ JS_PSG("desiredSize", ReadableStreamDefaultController_desiredSize, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamDefaultController_methods[] = {
+ JS_FN("close", ReadableStreamDefaultController_close, 0, 0),
+ JS_FN("enqueue", ReadableStreamDefaultController_enqueue, 1, 0),
+ JS_FN("error", ReadableStreamDefaultController_error, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamDefaultController, 4, 7, ClassSpec::DontDefineConstructor, 0,
+ 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 )
+ */
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerCancelSteps(JSContext* cx, HandleNativeObject controller,
+ HandleValue reason)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ // Step 1 of 3.10.5.1: If this.[[pendingPullIntos]] is not empty,
+ if (!controller->is<ReadableStreamDefaultController>()) {
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstDescriptor be the first element of
+ // this.[[pendingPullIntos]].
+ // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+ firstDescriptor->setBytesFilled(0);
+ }
+ }
+
+ // Step 1 of 3.8.5.1, step 2 of 3.10.5.1: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 2 of 3.8.5.1, step 3 of 3.10.5.1:
+ // Return ! PromiseInvokeOrNoop(this.[[underlying(Byte)Source]],
+ // "cancel", « reason »)
+ RootedValue underlyingSource(cx);
+ underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+
+ if (Is<TeeState>(underlyingSource)) {
+ Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
+ Rooted<ReadableStreamDefaultController*> defaultController(cx);
+ defaultController = &controller->as<ReadableStreamDefaultController>();
+ return ReadableStreamTee_Cancel(cx, teeState, defaultController, reason);
+ }
+
+ if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ void* source = underlyingSource.toPrivate();
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ RootedValue rval(cx);
+ rval = cx->runtime()->readableStreamCancelCallback(cx, stream, source,
+ stream->embeddingFlags(), reason);
+ return PromiseObject::unforgeableResolve(cx, rval);
+ }
+
+ return PromiseInvokeOrNoop(cx, underlyingSource, cx->names().cancel, reason);
+}
+
+inline static MOZ_MUST_USE bool
+DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk);
+
+// Streams spec, 3.8.5.2. ReadableStreamDefaultController [[PullSteps]]()
+static JSObject*
+ReadableStreamDefaultControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableStreamDefaultController>());
+
+ // Step 1: Let stream be this.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: If this.[[queue]] is not empty,
+ RootedNativeObject queue(cx);
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ if (val.isObject())
+ queue = &val.toObject().as<NativeObject>();
+
+ if (queue && queue->getDenseInitializedLength() != 0) {
+ // Step a: Let chunk be ! DequeueValue(this.[[queue]]).
+ RootedValue chunk(cx);
+ if (!DequeueValue(cx, controller, &chunk))
+ return nullptr;
+
+ // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
+ // perform ! ReadableStreamClose(stream).
+ bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
+ if (closeRequested && queue->getDenseInitializedLength() == 0) {
+ if (!ReadableStreamCloseInternal(cx, stream))
+ return nullptr;
+ }
+
+ // Step c: Otherwise, perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ else {
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+ }
+
+ // Step d: Return a promise resolved with ! CreateIterResultObject(chunk, false).
+ RootedObject iterResultObj(cx, CreateIterResultObject(cx, chunk, false));
+ if (!iterResultObj)
+ return nullptr;
+ RootedValue iterResult(cx, ObjectValue(*iterResultObj));
+ return PromiseObject::unforgeableResolve(cx, iterResult);
+ }
+
+ // Step 3: Let pendingPromise be ! ReadableStreamAddReadRequest(stream).
+ Rooted<PromiseObject*> pendingPromise(cx, ReadableStreamAddReadRequest(cx, stream));
+ if (!pendingPromise)
+ return nullptr;
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 5: Return pendingPromise.
+ return pendingPromise;
+}
+
+// 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);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+ uint32_t flags = ControllerFlags(controller);
+
+ // Step a: Set controller.[[pulling]] to false.
+ // Step b.i: Set controller.[[pullAgain]] to false.
+ RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);
+
+ // Step b: If controller.[[pullAgain]] is true,
+ if (flags & ControllerFlag_PullAgain) {
+ // Step ii: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ 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,
+static bool
+ControllerPullFailedHandler(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedNativeObject controller(cx, TargetFromHandler<NativeObject>(args.callee()));
+ HandleValue e = args.get(0);
+
+ // Step a: If controller.[[controlledReadableStream]].[[state]] is "readable",
+ // perform ! ReadableByteStreamControllerError(controller, e).
+ if (StreamFromController(controller)->readable()) {
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+ }
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamControllerShouldCallPull(NativeObject* controller);
+
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller);
+
+// Streams spec, 3.9.2 ReadableStreamDefaultControllerCallPullIfNeeded ( controller )
+// and
+// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
+inline static MOZ_MUST_USE bool
+ReadableStreamControllerCallPullIfNeeded(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 1: Let shouldPull be
+ // ! ReadableByteStreamControllerShouldCallPull(controller).
+ bool shouldPull = ReadableStreamControllerShouldCallPull(controller);
+
+ // Step 2: If shouldPull is false, return.
+ if (!shouldPull)
+ return true;
+
+ // Step 3: If controller.[[pulling]] is true,
+ if (ControllerFlags(controller) & ControllerFlag_Pulling) {
+ // Step a: Set controller.[[pullAgain]] to true.
+ AddControllerFlags(controller, ControllerFlag_PullAgain);
+
+ // Step b: Return.
+ return true;
+ }
+
+ // Step 4: Assert: controller.[[pullAgain]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_PullAgain));
+
+ // Step 5: Set controller.[[pulling]] to true.
+ AddControllerFlags(controller, ControllerFlag_Pulling);
+
+ // Step 6: Let pullPromise be
+ // ! PromiseInvokeOrNoop(controller.[[underlyingByteSource]], "pull", controller).
+ RootedObject pullPromise(cx);
+ RootedValue underlyingSource(cx);
+ underlyingSource = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+
+ if (Is<TeeState>(underlyingSource)) {
+ Rooted<TeeState*> teeState(cx, &underlyingSource.toObject().as<TeeState>());
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ pullPromise = ReadableStreamTee_Pull(cx, teeState, stream);
+ } else if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ void* source = underlyingSource.toPrivate();
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+ cx->runtime()->readableStreamDataRequestCallback(cx, stream, source,
+ stream->embeddingFlags(), desiredSize);
+ pullPromise = PromiseObject::unforgeableResolve(cx, UndefinedHandleValue);
+ } else {
+ pullPromise = PromiseInvokeOrNoop(cx, underlyingSource, cx->names().pull, controllerVal);
+ }
+ if (!pullPromise)
+ return false;
+
+ RootedObject onPullFulfilled(cx, NewHandler(cx, ControllerPullHandler, controller));
+ if (!onPullFulfilled)
+ return false;
+
+ RootedObject onPullRejected(cx, NewHandler(cx, ControllerPullFailedHandler, controller));
+ if (!onPullRejected)
+ return false;
+
+ return JS::AddPromiseReactions(cx, pullPromise, onPullFulfilled, onPullRejected);
+
+ // Steps 7-8 implemented in functions above.
+}
+
+// Streams spec, 3.9.3. ReadableStreamDefaultControllerShouldCallPull ( controller )
+// and
+// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
+static bool
+ReadableStreamControllerShouldCallPull(NativeObject* controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ ReadableStream* stream = StreamFromController(controller);
+
+ // 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())
+ return false;
+
+ // Step 3: If controller.[[closeRequested]] is true, return false.
+ uint32_t flags = ControllerFlags(controller);
+ if (flags & ControllerFlag_CloseRequested)
+ return false;
+
+ // Step 4: If controller.[[started]] is false, return false.
+ if (!(flags & ControllerFlag_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)
+ return true;
+
+ // Step 6: Let desiredSize be ReadableStreamDefaultControllerGetDesiredSize(controller).
+ double desiredSize = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+
+ // 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.4. ReadableStreamDefaultControllerClose ( controller )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerClose(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 4: Set controller.[[closeRequested]] to true.
+ AddControllerFlags(controller, ControllerFlag_CloseRequested);
+
+ // Step 5: If controller.[[queue]] is empty, perform ! ReadableStreamClose(stream).
+ RootedNativeObject queue(cx);
+ queue = &controller->getFixedSlot(QueueContainerSlot_Queue).toObject().as<NativeObject>();
+ if (queue->getDenseInitializedLength() == 0)
+ return ReadableStreamCloseInternal(cx, stream);
+
+ return true;
+}
+
+static MOZ_MUST_USE bool
+EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
+ HandleValue sizeVal);
+
+// Streams spec, 3.9.5. ReadableStreamDefaultControllerEnqueue ( controller, chunk )
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerEnqueue(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue chunk)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->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))
+ 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);
+ strategySize = controller->getFixedSlot(DefaultControllerSlot_StrategySize);
+ if (!strategySize.isUndefined()) {
+ // Step i: Set chunkSize to Call(stream.[[strategySize]], undefined, chunk).
+ success = Call(cx, strategySize, UndefinedHandleValue, chunk, &chunkSize);
+ }
+
+ // Step c: Let enqueueResult be
+ // EnqueueValueWithSize(controller, chunk, chunkSize).
+ if (success)
+ success = EnqueueValueWithSize(cx, controller, 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->getPendingException(&exn))
+ return false;
+
+ // Step b.ii.1: Perform
+ // ! ReadableStreamDefaultControllerErrorIfNeeded(controller,
+ // chunkSize.[[Value]]).
+ if (!ReadableStreamDefaultControllerErrorIfNeeded(cx, controller, exn))
+ return false;
+
+ // Step b.ii.2: Return chunkSize.
+ return false;
+ }
+ }
+
+ // Step 6: Perform ! ReadableStreamDefaultControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return false;
+
+ // Step 7: Return.
+ return true;
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.9.6. ReadableStreamDefaultControllerError ( controller, e )
+// and
+// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
+static MOZ_MUST_USE bool
+ReadableStreamControllerError(JSContext* cx, HandleNativeObject controller, HandleValue e)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->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))
+ return false;
+ }
+
+ // Step 3 (or 4): Perform ! ResetQueue(controller).
+ if (!ResetQueue(cx, controller))
+ return false;
+
+ // Step 4 (or 5): Perform ! ReadableStreamError(stream, e).
+ return ReadableStreamErrorInternal(cx, stream, e);
+}
+
+// Streams spec, 3.9.7. ReadableStreamDefaultControllerErrorIfNeeded ( controller, e ) nothrow
+static MOZ_MUST_USE bool
+ReadableStreamDefaultControllerErrorIfNeeded(JSContext* cx,
+ Handle<ReadableStreamDefaultController*> controller,
+ HandleValue e)
+{
+ // Step 1: If controller.[[controlledReadableStream]].[[state]] is "readable",
+ // perform ! ReadableStreamDefaultControllerError(controller, e).
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ if (stream->readable())
+ return ReadableStreamControllerError(cx, controller, e);
+ return true;
+}
+
+// Streams spec, 3.9.8. ReadableStreamDefaultControllerGetDesiredSize ( controller )
+// and
+// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
+static MOZ_MUST_USE double
+ReadableStreamControllerGetDesiredSizeUnchecked(NativeObject* controller)
+{
+ // Steps 1-4 done at callsites, so only assert that they have been done.
+#if DEBUG
+ ReadableStream* stream = StreamFromController(controller);
+ MOZ_ASSERT(!(stream->errored() || stream->closed()));
+#endif // DEBUG
+
+ // Step 5: Return controller.[[strategyHWM]] − controller.[[queueTotalSize]].
+ double strategyHWM = controller->getFixedSlot(ControllerSlot_StrategyHWM).toNumber();
+ double queueSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ return strategyHWM - queueSize;
+}
+
+// Streams spec, 3.10.3 new ReadableByteStreamController ( stream, underlyingSource,
+// highWaterMark )
+// Steps 3 - 16.
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue underlyingByteSource,
+ HandleValue highWaterMarkVal)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, underlyingByteSource);
+
+ // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(0));
+
+ // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
+ if (!ReadableByteStreamControllerClearPendingPullIntos(cx, controller))
+ return nullptr;
+
+ // Step 7: Perform ! ResetQueue(this).
+ if (!ResetQueue(cx, controller))
+ return nullptr;
+
+ // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
+ // These should be false by default, unchanged since step 5.
+ MOZ_ASSERT(ControllerFlags(controller) == 0);
+
+ // Step 9: Set this.[[strategyHWM]] to
+ // ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ double highWaterMark;
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, &highWaterMark))
+ return nullptr;
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, NumberValue(highWaterMark));
+
+ // Step 10: Let autoAllocateChunkSize be
+ // ? GetV(underlyingByteSource, "autoAllocateChunkSize").
+ RootedValue autoAllocateChunkSize(cx);
+ if (!GetProperty(cx, underlyingByteSource, cx->names().autoAllocateChunkSize,
+ &autoAllocateChunkSize))
+ {
+ return nullptr;
+ }
+
+ // Step 11: If autoAllocateChunkSize is not undefined,
+ if (!autoAllocateChunkSize.isUndefined()) {
+ // Step a: If ! IsInteger(autoAllocateChunkSize) is false, or if
+ // autoAllocateChunkSize ≤ 0, throw a RangeError exception.
+ if (!IsInteger(autoAllocateChunkSize) || autoAllocateChunkSize.toNumber() <= 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE);
+ return nullptr;
+ }
+ }
+
+ // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
+ controller->setFixedSlot(ByteControllerSlot_AutoAllocateSize, autoAllocateChunkSize);
+
+ // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
+ if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
+ return nullptr;
+
+ // Step 14: Let controller be this (implicit).
+
+ // Step 15: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ RootedValue startResult(cx);
+ RootedValue controllerVal(cx, ObjectValue(*controller));
+ if (!InvokeOrNoop(cx, underlyingByteSource, cx->names().start, controllerVal, &startResult))
+ return nullptr;
+
+ // Step 16: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, startResult));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+bool
+ReadableByteStreamController::hasExternalSource() {
+ return ControllerFlags(this) & ControllerFlag_ExternalSource;
+}
+
+// Streams spec, 3.10.3.
+// new ReadableByteStreamController ( stream, underlyingByteSource,
+// highWaterMark )
+bool
+ReadableByteStreamController::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableByteStreamController"))
+ return false;
+
+ // Step 1: If ! IsReadableStream(stream) is false, throw a TypeError exception.
+ HandleValue streamVal = args.get(0);
+ if (!Is<ReadableStream>(streamVal)) {
+ ReportArgTypeError(cx, "ReadableStreamDefaultController", "ReadableStream",
+ args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableStream*> stream(cx, &streamVal.toObject().as<ReadableStream>());
+
+ // Step 2: If stream.[[readableStreamController]] is not undefined, throw a
+ // TypeError exception.
+ if (HasController(stream)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_CONTROLLER_SET);
+ return false;
+ }
+
+ RootedObject controller(cx, CreateReadableByteStreamController(cx, stream, args.get(1),
+ args.get(2)));
+ if (!controller)
+ return false;
+
+ args.rval().setObject(*controller);
+ return true;
+}
+
+// Version of the ReadableByteStreamConstructor that's specialized for
+// handling external, embedding-provided, underlying sources.
+static MOZ_MUST_USE ReadableByteStreamController*
+CreateReadableByteStreamController(JSContext* cx, Handle<ReadableStream*> stream,
+ void* underlyingSource)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = NewBuiltinClassInstance<ReadableByteStreamController>(cx);
+ if (!controller)
+ return nullptr;
+
+ // Step 3: Set this.[[controlledReadableStream]] to stream.
+ controller->setFixedSlot(ControllerSlot_Stream, ObjectValue(*stream));
+
+ // Step 4: Set this.[[underlyingByteSource]] to underlyingByteSource.
+ controller->setFixedSlot(ControllerSlot_UnderlyingSource, PrivateValue(underlyingSource));
+
+ // Step 5: Set this.[[pullAgain]], and this.[[pulling]] to false.
+ controller->setFixedSlot(ControllerSlot_Flags, Int32Value(ControllerFlag_ExternalSource));
+
+ // Step 6: Perform ! ReadableByteStreamControllerClearPendingPullIntos(this).
+ // Omitted.
+
+ // Step 7: Perform ! ResetQueue(this).
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(0));
+
+ // Step 8: Set this.[[started]] and this.[[closeRequested]] to false.
+ // Step 9: Set this.[[strategyHWM]] to
+ // ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ controller->setFixedSlot(ControllerSlot_StrategyHWM, Int32Value(0));
+
+ // Step 10: Let autoAllocateChunkSize be
+ // ? GetV(underlyingByteSource, "autoAllocateChunkSize").
+ // Step 11: If autoAllocateChunkSize is not undefined,
+ // Step 12: Set this.[[autoAllocateChunkSize]] to autoAllocateChunkSize.
+ // Omitted.
+
+ // Step 13: Set this.[[pendingPullIntos]] to a new empty List.
+ if (!SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos))
+ return nullptr;
+
+ // Step 14: Let controller be this (implicit).
+ // Step 15: Let startResult be
+ // ? InvokeOrNoop(underlyingSource, "start", « this »).
+ // Omitted.
+
+ // Step 16: Let startPromise be a promise resolved with startResult:
+ RootedObject startPromise(cx, PromiseObject::unforgeableResolve(cx, UndefinedHandleValue));
+ if (!startPromise)
+ return nullptr;
+
+ RootedObject onStartFulfilled(cx, NewHandler(cx, ControllerStartHandler, controller));
+ if (!onStartFulfilled)
+ return nullptr;
+
+ RootedObject onStartRejected(cx, NewHandler(cx, ControllerStartFailedHandler, controller));
+ if (!onStartRejected)
+ return nullptr;
+
+ if (!JS::AddPromiseReactions(cx, startPromise, onStartFulfilled, onStartRejected))
+ return nullptr;
+
+ return controller;
+}
+
+static MOZ_MUST_USE ReadableStreamBYOBRequest*
+CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
+ HandleObject view);
+
+// Streams spec, 3.10.4.1. get byobRequest
+static MOZ_MUST_USE bool
+ReadableByteStreamController_byobRequest_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+
+ // Step 2: If this.[[byobRequest]] is undefined and this.[[pendingPullIntos]]
+ // is not empty,
+ Value val = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
+ RootedValue byobRequest(cx, val);
+ val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+
+ if (byobRequest.isUndefined() && pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstDescriptor be the first element of this.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step b: Let view be ! Construct(%Uint8Array%,
+ // « firstDescriptor.[[buffer]],
+ // firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]],
+ // firstDescriptor.[[byteLength]] − firstDescriptor.[[bytesFilled]] »).
+ RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
+ uint32_t bytesFilled = firstDescriptor->bytesFilled();
+ RootedObject view(cx, JS_NewUint8ArrayWithBuffer(cx, buffer,
+ firstDescriptor->byteOffset() + bytesFilled,
+ firstDescriptor->byteLength() - bytesFilled));
+ if (!view)
+ return false;
+
+ // Step c: Set this.[[byobRequest]] to
+ // ! Construct(ReadableStreamBYOBRequest, « this, view »).
+ RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
+ if (!request)
+ return false;
+ byobRequest = ObjectValue(*request);
+ controller->setFixedSlot(ByteControllerSlot_BYOBRequest, byobRequest);
+ }
+
+ // Step 3: Return this.[[byobRequest]].
+ args.rval().set(byobRequest);
+ return true;
+}
+
+static bool
+ReadableByteStreamController_byobRequest(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If IsReadableByteStreamController(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_byobRequest_impl>(cx, args);
+}
+
+// Streams spec, 3.10.4.2. get desiredSize
+// Combined with 3.8.4.1 above.
+
+static bool
+ReadableByteStreamController_desiredSize(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableStreamController_desiredSize_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller);
+
+// Streams spec, 3.10.4.3. close()
+static MOZ_MUST_USE bool
+ReadableByteStreamController_close_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+
+ // Steps 2-3.
+ if (!VerifyControllerStateForClosing(cx, controller))
+ return false;
+
+ // Step 4: Perform ? ReadableByteStreamControllerClose(this).
+ if (!ReadableByteStreamControllerClose(cx, controller))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_close(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_close_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject chunk);
+
+// Streams spec, 3.10.4.4. enqueue ( chunk )
+static MOZ_MUST_USE bool
+ReadableByteStreamController_enqueue_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+ HandleValue chunkVal = args.get(0);
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
+ return false;
+ }
+
+ // Step 4: If Type(chunk) is not Object, throw a TypeError exception.
+ // Step 5: If chunk does not have a [[ViewedArrayBuffer]] internal slot,
+ // throw a TypeError exception.
+ if (!chunkVal.isObject() || !JS_IsArrayBufferViewObject(&chunkVal.toObject())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableByteStreamController#enqueue");
+ return false;
+ }
+ RootedObject chunk(cx, &chunkVal.toObject());
+
+ // Step 6: Return ! ReadableByteStreamControllerEnqueue(this, chunk).
+ if (!ReadableByteStreamControllerEnqueue(cx, controller, chunk))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_enqueue(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_enqueue_impl>(cx, args);
+}
+
+// Streams spec, 3.10.4.5. error ( e )
+static MOZ_MUST_USE bool
+ReadableByteStreamController_error_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &args.thisv().toObject().as<ReadableByteStreamController>();
+ HandleValue e = args.get(0);
+
+ // Step 2: Let stream be this.[[controlledReadableStream]].
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableByteStreamControllerError(this, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableByteStreamController_error(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableByteStreamController(this) is false, throw a
+ // TypeError exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableByteStreamController>,
+ ReadableByteStreamController_error_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableByteStreamController_properties[] = {
+ JS_PSG("byobRequest", ReadableByteStreamController_byobRequest, 0),
+ JS_PSG("desiredSize", ReadableByteStreamController_desiredSize, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableByteStreamController_methods[] = {
+ JS_FN("close", ReadableByteStreamController_close, 0, 0),
+ JS_FN("enqueue", ReadableByteStreamController_enqueue, 1, 0),
+ JS_FN("error", ReadableByteStreamController_error, 1, 0),
+ JS_FS_END
+};
+
+static void
+ReadableByteStreamControllerFinalize(FreeOp* fop, JSObject* obj)
+{
+ ReadableByteStreamController& controller = obj->as<ReadableByteStreamController>();
+
+ if (controller.getFixedSlot(ControllerSlot_Flags).isUndefined())
+ return;
+
+ uint32_t flags = ControllerFlags(&controller);
+ if (!(flags & ControllerFlag_ExternalSource))
+ return;
+
+ uint8_t embeddingFlags = flags >> ControllerEmbeddingFlagsOffset;
+
+ void* underlyingSource = controller.getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ obj->runtimeFromAnyThread()->readableStreamFinalizeCallback(underlyingSource, embeddingFlags);
+}
+
+static const ClassOps ReadableByteStreamControllerClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ ReadableByteStreamControllerFinalize,
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ nullptr, /* trace */
+};
+
+CLASS_SPEC(ReadableByteStreamController, 3, 9, ClassSpec::DontDefineConstructor,
+ JSCLASS_BACKGROUND_FINALIZE, &ReadableByteStreamControllerClassOps);
+
+// Streams spec, 3.10.5.1. [[PullSteps]] ()
+// Unified with 3.8.5.1 above.
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec, 3.10.5.2. [[PullSteps]] ()
+static JSObject*
+ReadableByteStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ // Step 1: Let stream be this.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: MOZ_ASSERT: ! ReadableStreamHasDefaultReader(stream) is true.
+ MOZ_ASSERT(ReadableStreamHasDefaultReader(stream));
+
+ RootedValue val(cx);
+ // Step 3: If this.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step 3.a: MOZ_ASSERT: ! ReadableStreamGetNumReadRequests(_stream_) is 0.
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(stream) == 0);
+
+ RootedObject view(cx);
+
+ if (stream->mode() == JS::ReadableStreamMode::ExternalSource) {
+ val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ view = JS_NewUint8Array(cx, queueTotalSize);
+ if (!view)
+ return nullptr;
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(view, &dummy, noGC);
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ // TODO: use bytesWritten to correctly update the request's state.
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), 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).
+ val = controller->getFixedSlot(QueueContainerSlot_Queue);
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ Rooted<ByteStreamChunk*> entry(cx, ShiftFromList<ByteStreamChunk>(cx, queue));
+ MOZ_ASSERT(entry);
+
+ queueTotalSize = queueTotalSize - entry->byteLength();
+
+ // Step 3.f: Let view be ! Construct(%Uint8Array%, « entry.[[buffer]],
+ // entry.[[byteOffset]], entry.[[byteLength]] »).
+ // (reordered)
+ RootedObject buffer(cx, entry->buffer());
+
+ uint32_t byteOffset = entry->byteOffset();
+ view = JS_NewUint8ArrayWithBuffer(cx, buffer, byteOffset, entry->byteLength());
+ if (!view)
+ return nullptr;
+ }
+
+ // Step 3.d: Set this.[[queueTotalSize]] to
+ // this.[[queueTotalSize]] − entry.[[byteLength]].
+ // (reordered)
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));
+
+ // Step 3.e: Perform ! ReadableByteStreamControllerHandleQueueDrain(this).
+ // (reordered)
+ if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
+ return nullptr;
+
+ // Step 3.g: Return a promise resolved with ! CreateIterResultObject(view, false).
+ val.setObject(*view);
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
+ if (!iterResult)
+ return nullptr;
+ val.setObject(*iterResult);
+
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step 4: Let autoAllocateChunkSize be this.[[autoAllocateChunkSize]].
+ val = controller->getFixedSlot(ByteControllerSlot_AutoAllocateSize);
+
+ // Step 5: If autoAllocateChunkSize is not undefined,
+ if (!val.isUndefined()) {
+ double autoAllocateChunkSize = val.toNumber();
+
+ // Step 5.a: Let buffer be Construct(%ArrayBuffer%, « autoAllocateChunkSize »).
+ RootedObject bufferObj(cx, JS_NewArrayBuffer(cx, autoAllocateChunkSize));
+
+ // Step 5.b: If buffer is an abrupt completion,
+ // return a promise rejected with buffer.[[Value]].
+ if (!bufferObj)
+ return PromiseRejectedWithPendingError(cx);
+
+ RootedArrayBufferObject buffer(cx, &bufferObj->as<ArrayBufferObject>());
+
+ // Step 5.c: Let pullIntoDescriptor be Record {[[buffer]]: buffer.[[Value]],
+ // [[byteOffset]]: 0,
+ // [[byteLength]]: autoAllocateChunkSize,
+ // [[bytesFilled]]: 0, [[elementSize]]: 1,
+ // [[ctor]]: %Uint8Array%,
+ // [[readerType]]: `"default"`}.
+ RootedObject pullIntoDescriptor(cx);
+ pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, 0,
+ autoAllocateChunkSize, 0, 1,
+ nullptr,
+ ReaderType_Default);
+ if (!pullIntoDescriptor)
+ return PromiseRejectedWithPendingError(cx);
+
+ // Step 5.d: Append pullIntoDescriptor as the last element of this.[[pendingPullIntos]].
+ val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+ }
+
+ // Step 6: Let promise be ! ReadableStreamAddReadRequest(stream).
+ Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadRequest(cx, stream));
+ if (!promise)
+ return nullptr;
+
+ // Step 7: Perform ! ReadableByteStreamControllerCallPullIfNeeded(this).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 8: Return promise.
+ return promise;
+}
+
+/**
+ * Unified implementation of ReadableStream controllers' [[PullSteps]] internal
+ * methods.
+ * Streams spec, 3.8.5.2. [[PullSteps]] ()
+ * and
+ * Streams spec, 3.10.5.2. [[PullSteps]] ()
+ */
+static MOZ_MUST_USE JSObject*
+ReadableStreamControllerPullSteps(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(IsReadableStreamController(controller));
+
+ if (controller->is<ReadableStreamDefaultController>())
+ return ReadableStreamDefaultControllerPullSteps(cx, controller);
+
+ return ReadableByteStreamControllerPullSteps(cx, controller);
+}
+
+
+static MOZ_MUST_USE ReadableStreamBYOBRequest*
+CreateReadableStreamBYOBRequest(JSContext* cx, Handle<ReadableByteStreamController*> controller,
+ HandleObject view)
+{
+ MOZ_ASSERT(controller);
+ MOZ_ASSERT(JS_IsArrayBufferViewObject(view));
+
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = NewBuiltinClassInstance<ReadableStreamBYOBRequest>(cx);
+ if (!request)
+ return nullptr;
+
+ // Step 1: Set this.[[associatedReadableByteStreamController]] to controller.
+ request->setFixedSlot(BYOBRequestSlot_Controller, ObjectValue(*controller));
+
+ // Step 2: Set this.[[view]] to view.
+ request->setFixedSlot(BYOBRequestSlot_View, ObjectValue(*view));
+
+ return request;
+}
+
+// Streams spec, 3.11.3. new ReadableStreamBYOBRequest ( controller, view )
+bool
+ReadableStreamBYOBRequest::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ HandleValue controllerVal = args.get(0);
+ HandleValue viewVal = args.get(1);
+
+ if (!ThrowIfNotConstructing(cx, args, "ReadableStreamBYOBRequest"))
+ return false;
+
+ // TODO: open PR against spec to add these checks.
+ // They're expected to have happened in code using requests.
+ if (!Is<ReadableByteStreamController>(controllerVal)) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBRequest",
+ "ReadableByteStreamController", args.get(0));
+ return false;
+ }
+
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+
+ if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
+ ReportArgTypeError(cx, "ReadableStreamBYOBRequest", "ArrayBuffer view",
+ args.get(1));
+ return false;
+ }
+
+ RootedArrayBufferObject view(cx, &viewVal.toObject().as<ArrayBufferObject>());
+
+ RootedObject request(cx, CreateReadableStreamBYOBRequest(cx, controller, view));
+ if (!request)
+ return false;
+
+ args.rval().setObject(*request);
+ return true;
+}
+
+// Streams spec, 3.11.4.1 get view
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_view_impl(JSContext* cx, const CallArgs& args)
+{
+ // Step 2: Return this.[[view]].
+ NativeObject* request = &args.thisv().toObject().as<NativeObject>();
+ args.rval().set(request->getFixedSlot(BYOBRequestSlot_View));
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_view(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_view_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespond(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleValue bytesWrittenVal);
+
+// Streams spec, 3.11.4.2. respond ( bytesWritten )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_respond_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
+ HandleValue bytesWritten = args.get(0);
+
+ // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
+ // throw a TypeError exception.
+ RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
+ if (controllerVal.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respond");
+ return false;
+ }
+
+ // Step 3: Return ?
+ // ReadableByteStreamControllerRespond(this.[[associatedReadableByteStreamController]],
+ // bytesWritten).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+
+ if (!ReadableByteStreamControllerRespond(cx, controller, bytesWritten))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_respond(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_respond_impl>(cx, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject view);
+
+// Streams spec, 3.11.4.3. respondWithNewView ( view )
+static MOZ_MUST_USE bool
+ReadableStreamBYOBRequest_respondWithNewView_impl(JSContext* cx, const CallArgs& args)
+{
+ Rooted<ReadableStreamBYOBRequest*> request(cx);
+ request = &args.thisv().toObject().as<ReadableStreamBYOBRequest>();
+ HandleValue viewVal = args.get(0);
+
+ // Step 2: If this.[[associatedReadableByteStreamController]] is undefined,
+ // throw a TypeError exception.
+ RootedValue controllerVal(cx, request->getFixedSlot(BYOBRequestSlot_Controller));
+ if (controllerVal.isUndefined()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, "respondWithNewView");
+ return false;
+ }
+
+ // Step 3: If Type(chunk) is not Object, throw a TypeError exception.
+ // Step 4: If view does not have a [[ViewedArrayBuffer]] internal slot, throw
+ // a TypeError exception.
+ if (!viewVal.isObject() || !JS_IsArrayBufferViewObject(&viewVal.toObject())) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "ReadableStreamBYOBRequest#respondWithNewView");
+ return false;
+ }
+
+ // Step 5: Return ?
+ // ReadableByteStreamControllerRespondWithNewView(this.[[associatedReadableByteStreamController]],
+ // view).
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerVal.toObject().as<ReadableByteStreamController>();
+ RootedObject view(cx, &viewVal.toObject());
+
+ if (!ReadableByteStreamControllerRespondWithNewView(cx, controller, view))
+ return false;
+
+ args.rval().setUndefined();
+ return true;
+}
+
+static bool
+ReadableStreamBYOBRequest_respondWithNewView(JSContext* cx, unsigned argc, Value* vp)
+{
+ // Step 1: If ! IsReadableStreamBYOBRequest(this) is false, throw a TypeError
+ // exception.
+ CallArgs args = CallArgsFromVp(argc, vp);
+ return CallNonGenericMethod<Is<ReadableStreamBYOBRequest>,
+ ReadableStreamBYOBRequest_respondWithNewView_impl>(cx, args);
+}
+
+static const JSPropertySpec ReadableStreamBYOBRequest_properties[] = {
+ JS_PSG("view", ReadableStreamBYOBRequest_view, 0),
+ JS_PS_END
+};
+
+static const JSFunctionSpec ReadableStreamBYOBRequest_methods[] = {
+ JS_FN("respond", ReadableStreamBYOBRequest_respond, 1, 0),
+ JS_FN("respondWithNewView", ReadableStreamBYOBRequest_respondWithNewView, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ReadableStreamBYOBRequest, 3, 2, ClassSpec::DontDefineConstructor, 0,
+ JS_NULL_CLASS_OPS);
+
+// Streams spec, 3.12.1. IsReadableStreamBYOBRequest ( x )
+// Implemented via is<ReadableStreamBYOBRequest>()
+
+// Streams spec, 3.12.2. IsReadableByteStreamController ( x )
+// Implemented via is<ReadableByteStreamController>()
+
+// Streams spec, 3.12.3. ReadableByteStreamControllerCallPullIfNeeded ( controller )
+// Unified with 3.9.2 above.
+
+static void
+ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller);
+
+// Streams spec, 3.12.4. ReadableByteStreamControllerClearPendingPullIntos ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClearPendingPullIntos(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 2: Set controller.[[pendingPullIntos]] to a new empty List.
+ return SetNewList(cx, controller, ByteControllerSlot_PendingPullIntos);
+}
+
+// Streams spec, 3.12.5. ReadableByteStreamControllerClose ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerClose(JSContext* cx, Handle<ReadableByteStreamController*> controller)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step 4: If controller.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step a: Set controller.[[closeRequested]] to true.
+ AddControllerFlags(controller, ControllerFlag_CloseRequested);
+
+ // Step b: Return
+ return true;
+ }
+
+ // Step 5: If controller.[[pendingPullIntos]] is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Let firstPendingPullInto be the first element of
+ // controller.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstPendingPullInto(cx);
+ firstPendingPullInto = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
+ if (firstPendingPullInto->bytesFilled() > 0) {
+ // Step i: Let e be a new TypeError exception. {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL);
+ RootedValue e(cx);
+ // Not much we can do about uncatchable exceptions, just bail.
+ if (!cx->getPendingException(&e))
+ return false;
+ // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return false;
+
+ // Step iii: Throw e.
+ return false;
+ }
+ }
+
+ // Step 6: Perform ! ReadableStreamClose(stream).
+ return ReadableStreamCloseInternal(cx, stream);
+}
+
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor);
+
+// Streams spec, 3.12.6. ReadableByteStreamControllerCommitPullIntoDescriptor ( stream, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerCommitPullIntoDescriptor(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: MOZ_ASSERT: stream.[[state]] is not "errored".
+ MOZ_ASSERT(!stream->errored());
+
+ // Step 2: Let done be false.
+ bool done = false;
+
+ // Step 3: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: MOZ_ASSERT: pullIntoDescriptor.[[bytesFilled]] is 0.
+ MOZ_ASSERT(pullIntoDescriptor->bytesFilled() == 0);
+
+ // Step b: Set done to true.
+ done = true;
+ }
+
+ // Step 4: Let filledView be
+ // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ RootedObject filledView(cx);
+ filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx, pullIntoDescriptor);
+ if (!filledView)
+ return false;
+
+ // Step 5: If pullIntoDescriptor.[[readerType]] is "default",
+ uint32_t readerType = pullIntoDescriptor->readerType();
+ RootedValue filledViewVal(cx, ObjectValue(*filledView));
+ if (readerType == ReaderType_Default) {
+ // Step a: Perform ! ReadableStreamFulfillReadRequest(stream, filledView, done).
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
+ return false;
+ } else {
+ // Step 6: Otherwise,
+ // Step a: MOZ_ASSERT: pullIntoDescriptor.[[readerType]] is "byob".
+ MOZ_ASSERT(readerType == ReaderType_BYOB);
+
+ // Step b: Perform ! ReadableStreamFulfillReadIntoRequest(stream, filledView, done).
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, filledViewVal, done))
+ return false;
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.7. ReadableByteStreamControllerConvertPullIntoDescriptor ( pullIntoDescriptor )
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerConvertPullIntoDescriptor(JSContext* cx,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: Let bytesFilled be pullIntoDescriptor.[[bytesFilled]].
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+
+ // Step 2: Let elementSize be pullIntoDescriptor.[[elementSize]].
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+
+ // Step 3: Assert: bytesFilled <= pullIntoDescriptor.[[byteLength]].
+ MOZ_ASSERT(bytesFilled <= pullIntoDescriptor->byteLength());
+
+ // Step 4: Assert: bytesFilled mod elementSize is 0.
+ MOZ_ASSERT(bytesFilled % elementSize == 0);
+
+ // Step 5: Return ! Construct(pullIntoDescriptor.[[ctor]],
+ // pullIntoDescriptor.[[buffer]],
+ // pullIntoDescriptor.[[byteOffset]],
+ // bytesFilled / elementSize).
+ RootedObject ctor(cx, pullIntoDescriptor->ctor());
+ if (!ctor) {
+ if (!GetBuiltinConstructor(cx, JSProto_Uint8Array, &ctor))
+ return nullptr;
+ }
+ RootedObject buffer(cx, pullIntoDescriptor->buffer());
+ uint32_t byteOffset = pullIntoDescriptor->byteOffset();
+ FixedConstructArgs<3> args(cx);
+ args[0].setObject(*buffer);
+ args[1].setInt32(byteOffset);
+ args[2].setInt32(bytesFilled / elementSize);
+ return JS_New(cx, ctor, args);
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength);
+
+static MOZ_MUST_USE ArrayBufferObject*
+TransferArrayBuffer(JSContext* cx, HandleObject buffer);
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller);
+
+// Streams spec, 3.12.8. ReadableByteStreamControllerEnqueue ( controller, chunk )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject chunk)
+{
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 3: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // To make enqueuing chunks via JSAPI nicer, we want to be able to deal
+ // with ArrayBuffer objects in addition to ArrayBuffer views here.
+ // This cannot happen when enqueuing happens via
+ // ReadableByteStreamController_enqueue because that throws if invoked
+ // with anything but an ArrayBuffer view.
+
+ Rooted<ArrayBufferObject*> buffer(cx);
+ uint32_t byteOffset;
+ uint32_t byteLength;
+
+ if (chunk->is<ArrayBufferObject>()) {
+ // Steps 4-6 for ArrayBuffer objects.
+ buffer = &chunk->as<ArrayBufferObject>();
+ byteOffset = 0;
+ byteLength = buffer->byteLength();
+ } else {
+ // Step 4: Let buffer be chunk.[[ViewedArrayBuffer]].
+ bool dummy;
+ JSObject* bufferObj = JS_GetArrayBufferViewBuffer(cx, chunk, &dummy);
+ if (!bufferObj)
+ return false;
+ buffer = &bufferObj->as<ArrayBufferObject>();
+
+ // Step 5: Let byteOffset be chunk.[[ByteOffset]].
+ byteOffset = JS_GetArrayBufferViewByteOffset(chunk);
+
+ // Step 6: Let byteLength be chunk.[[ByteLength]].
+ byteLength = JS_GetArrayBufferViewByteLength(chunk);
+ }
+
+ // Step 7: Let transferredBuffer be ! TransferArrayBuffer(buffer).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+
+ // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
+ if (ReadableStreamHasDefaultReader(stream)) {
+ // Step a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ if (ReadableStreamGetNumReadRequests(stream) == 0) {
+ // Step i: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: controller.[[queue]] is empty.
+#if DEBUG
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(queue->getDenseInitializedLength() == 0);
+#endif // DEBUG
+
+ // Step ii: Let transferredView be
+ // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
+ RootedObject transferredView(cx, JS_NewUint8ArrayWithBuffer(cx, transferredBuffer,
+ byteOffset, byteLength));
+ if (!transferredView)
+ return false;
+
+ // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
+ RootedValue chunk(cx, ObjectValue(*transferredView));
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
+ return false;
+ }
+ } else if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 9: Otherwise,
+ // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
+ // Step i: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+
+ // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
+ return false;
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!stream->locked());
+
+ // Step ii: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, transferredBuffer,
+ byteOffset, byteLength))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.9.
+// ReadableByteStreamControllerEnqueueChunkToQueue ( controller, buffer,
+// byteOffset, byteLength )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerEnqueueChunkToQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject buffer, uint32_t byteOffset,
+ uint32_t byteLength)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>(), "must operate on ReadableByteStreamController");
+
+ // Step 1: Append Record {[[buffer]]: buffer,
+ // [[byteOffset]]: byteOffset,
+ // [[byteLength]]: byteLength}
+ // as the last element of controller.[[queue]].
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ Rooted<ByteStreamChunk*> chunk(cx);
+ chunk = ByteStreamChunk::create(cx, buffer, byteOffset, byteLength);
+ if (!chunk)
+ return false;
+
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ if (!AppendToList(cx, queue, chunkVal))
+ return false;
+
+ // Step 2: Add byteLength to controller.[[queueTotalSize]].
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ controller->setFixedSlot(QueueContainerSlot_TotalSize,
+ NumberValue(queueTotalSize + byteLength));
+
+ return true;
+}
+
+// Streams spec, 3.12.10. ReadableByteStreamControllerError ( controller, e )
+// Unified with 3.9.6 above.
+
+// Streams spec, 3.12.11. ReadableByteStreamControllerFillHeadPullIntoDescriptor ( controler, size, pullIntoDescriptor )
+static void
+ReadableByteStreamControllerFillHeadPullIntoDescriptor(ReadableByteStreamController* controller, uint32_t size,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: Assert: either controller.[[pendingPullIntos]] is empty, or the
+ // first element of controller.[[pendingPullIntos]] is pullIntoDescriptor.
+#if DEBUG
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ NativeObject* pendingPullIntos = &val.toObject().as<NativeObject>();
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() == 0 ||
+ &pendingPullIntos->getDenseElement(0).toObject() == pullIntoDescriptor);
+#endif // DEBUG
+
+ // Step 2: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 3: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] + size.
+ pullIntoDescriptor->setBytesFilled(pullIntoDescriptor->bytesFilled() + size);
+}
+
+// Streams spec, 3.12.12. ReadableByteStreamControllerFillPullIntoDescriptorFromQueue ( controller, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor,
+ bool* ready)
+{
+ *ready = false;
+
+ // Step 1: Let elementSize be pullIntoDescriptor.[[elementSize]].
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+
+ // Step 2: Let currentAlignedBytes be pullIntoDescriptor.[[bytesFilled]] −
+ // (pullIntoDescriptor.[[bytesFilled]] mod elementSize).
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+ uint32_t currentAlignedBytes = bytesFilled - (bytesFilled % elementSize);
+
+ // Step 3: Let maxBytesToCopy be min(controller.[[queueTotalSize]],
+ // pullIntoDescriptor.[[byteLength]] − pullIntoDescriptor.[[bytesFilled]]).
+ uint32_t byteLength = pullIntoDescriptor->byteLength();
+
+ // The queue size could be negative or overflow uint32_t. We cannot
+ // validly have a maxBytesToCopy value that'd overflow uint32_t, though,
+ // so just clamp to that.
+ Value sizeVal = controller->getFixedSlot(QueueContainerSlot_TotalSize);
+ uint32_t queueTotalSize = JS::ToUint32(sizeVal.toNumber());
+ uint32_t maxBytesToCopy = std::min(queueTotalSize, byteLength - bytesFilled);
+
+ // Step 4: Let maxBytesFilled be pullIntoDescriptor.[[bytesFilled]] + maxBytesToCopy.
+ uint32_t maxBytesFilled = bytesFilled + maxBytesToCopy;
+
+ // Step 5: Let maxAlignedBytes be maxBytesFilled − (maxBytesFilled mod elementSize).
+ uint32_t maxAlignedBytes = maxBytesFilled - (maxBytesFilled % elementSize);
+
+ // Step 6: Let totalBytesToCopyRemaining be maxBytesToCopy.
+ uint32_t totalBytesToCopyRemaining = maxBytesToCopy;
+
+ // Step 7: Let ready be false (implicit).
+
+ // Step 8: If maxAlignedBytes > currentAlignedBytes,
+ if (maxAlignedBytes > currentAlignedBytes) {
+ // Step a: Set totalBytesToCopyRemaining to maxAlignedBytes −
+ // pullIntoDescriptor.[[bytesFilled]].
+ totalBytesToCopyRemaining = maxAlignedBytes - bytesFilled;
+
+ // Step b: Let ready be true.
+ *ready = true;
+ }
+
+ if (ControllerFlags(controller) & ControllerFlag_ExternalSource) {
+ // TODO: it probably makes sense to eagerly drain the underlying source.
+ // We have a buffer lying around anyway, whereas the source might be
+ // able to free or reuse buffers once their content is copied into
+ // our buffer.
+ if (!ready)
+ return true;
+
+ Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ uint8_t* buffer = JS_GetArrayBufferData(targetBuffer, &dummy, noGC);
+ buffer += bytesFilled;
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
+ totalBytesToCopyRemaining, &bytesWritten);
+ pullIntoDescriptor->setBytesFilled(bytesFilled + bytesWritten);
+ }
+
+ queueTotalSize -= bytesWritten;
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(queueTotalSize));
+
+ return true;
+ }
+
+ // Step 9: Let queue be controller.[[queue]].
+ RootedValue val(cx, controller->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ // Step 10: Repeat the following steps while totalBytesToCopyRemaining > 0,
+ Rooted<ByteStreamChunk*> headOfQueue(cx);
+ while (totalBytesToCopyRemaining > 0) {
+ MOZ_ASSERT(queue->getDenseInitializedLength() != 0);
+
+ // Step a: Let headOfQueue be the first element of queue.
+ headOfQueue = PeekList<ByteStreamChunk>(queue);
+
+ // Step b: Let bytesToCopy be min(totalBytesToCopyRemaining,
+ // headOfQueue.[[byteLength]]).
+ uint32_t byteLength = headOfQueue->byteLength();
+ uint32_t bytesToCopy = std::min(totalBytesToCopyRemaining, byteLength);
+
+ // Step c: Let destStart be pullIntoDescriptor.[[byteOffset]] +
+ // pullIntoDescriptor.[[bytesFilled]].
+ uint32_t destStart = pullIntoDescriptor->byteOffset() + bytesFilled;
+
+ // Step d: Perform ! CopyDataBlockBytes(pullIntoDescriptor.[[buffer]].[[ArrayBufferData]],
+ // destStart,
+ // headOfQueue.[[buffer]].[[ArrayBufferData]],
+ // headOfQueue.[[byteOffset]],
+ // bytesToCopy).
+ RootedArrayBufferObject sourceBuffer(cx, headOfQueue->buffer());
+ uint32_t sourceOffset = headOfQueue->byteOffset();
+ RootedArrayBufferObject targetBuffer(cx, pullIntoDescriptor->buffer());
+ ArrayBufferObject::copyData(targetBuffer, destStart, sourceBuffer, sourceOffset,
+ bytesToCopy);
+
+ // Step e: If headOfQueue.[[byteLength]] is bytesToCopy,
+ if (byteLength == bytesToCopy) {
+ // Step i: Remove the first element of queue, shifting all other elements
+ // downward (so that the second becomes the first, and so on).
+ headOfQueue = ShiftFromList<ByteStreamChunk>(cx, queue);
+ MOZ_ASSERT(headOfQueue);
+ } else {
+ // Step f: Otherwise,
+ // Step i: Set headOfQueue.[[byteOffset]] to headOfQueue.[[byteOffset]] +
+ // bytesToCopy.
+ headOfQueue->SetByteOffset(sourceOffset + bytesToCopy);
+
+ // Step ii: Set headOfQueue.[[byteLength]] to headOfQueue.[[byteLength]] −
+ // bytesToCopy.
+ headOfQueue->SetByteLength(byteLength - bytesToCopy);
+ }
+
+ // Step g: Set controller.[[queueTotalSize]] to
+ // controller.[[queueTotalSize]] − bytesToCopy.
+ queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ queueTotalSize -= bytesToCopy;
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(queueTotalSize));
+
+ // Step h: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesToCopy,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesToCopy,
+ pullIntoDescriptor);
+ bytesFilled += bytesToCopy;
+ MOZ_ASSERT(bytesFilled == pullIntoDescriptor->bytesFilled());
+
+ // Step i: Set totalBytesToCopyRemaining to totalBytesToCopyRemaining − bytesToCopy.
+ totalBytesToCopyRemaining -= bytesToCopy;
+ }
+
+ // Step 11: If ready is false,
+ if (!*ready) {
+ // Step a: Assert: controller.[[queueTotalSize]] is 0.
+ MOZ_ASSERT(controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber() == 0);
+
+ // Step b: Assert: pullIntoDescriptor.[[bytesFilled]] > 0.
+ MOZ_ASSERT(bytesFilled > 0, "should have filled some bytes");
+
+ // Step c: Assert: pullIntoDescriptor.[[bytesFilled]] <
+ // pullIntoDescriptor.[[elementSize]].
+ MOZ_ASSERT(bytesFilled < elementSize);
+ }
+
+ // Step 12: Return ready.
+ return true;
+}
+
+// Streams spec 3.12.13. ReadableByteStreamControllerGetDesiredSize ( controller )
+// Unified with 3.9.8 above.
+
+// Streams spec, 3.12.14. ReadableByteStreamControllerHandleQueueDrain ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerHandleQueueDrain(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Assert: controller.[[controlledReadableStream]].[[state]] is "readable".
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ MOZ_ASSERT(stream->readable());
+
+ // Step 2: If controller.[[queueTotalSize]] is 0 and
+ // controller.[[closeRequested]] is true,
+ double totalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ bool closeRequested = ControllerFlags(controller) & ControllerFlag_CloseRequested;
+ if (totalSize == 0 && closeRequested) {
+ // Step a: Perform ! ReadableStreamClose(controller.[[controlledReadableStream]]).
+ return ReadableStreamCloseInternal(cx, stream);
+ }
+
+ // Step 3: Otherwise,
+ // Step a: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ return ReadableStreamControllerCallPullIfNeeded(cx, controller);
+}
+
+// Streams spec 3.12.15. ReadableByteStreamControllerInvalidateBYOBRequest ( controller )
+static void
+ReadableByteStreamControllerInvalidateBYOBRequest(NativeObject* controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: If controller.[[byobRequest]] is undefined, return.
+ Value byobRequestVal = controller->getFixedSlot(ByteControllerSlot_BYOBRequest);
+ if (byobRequestVal.isUndefined())
+ return;
+
+ NativeObject* byobRequest = &byobRequestVal.toObject().as<NativeObject>();
+ // Step 2: Set controller.[[byobRequest]].[[associatedReadableByteStreamController]]
+ // to undefined.
+ byobRequest->setFixedSlot(BYOBRequestSlot_Controller, UndefinedValue());
+
+ // Step 3: Set controller.[[byobRequest]].[[view]] to undefined.
+ byobRequest->setFixedSlot(BYOBRequestSlot_View, UndefinedValue());
+
+ // Step 4: Set controller.[[byobRequest]] to undefined.
+ controller->setFixedSlot(ByteControllerSlot_BYOBRequest, UndefinedValue());
+}
+
+static MOZ_MUST_USE PullIntoDescriptor*
+ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller);
+
+// Streams spec 3.12.16. ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue ( controller )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller)
+{
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 1: Assert: controller.[[closeRequested]] is false.
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+
+ // Step 2: Repeat the following steps while controller.[[pendingPullIntos]]
+ // is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
+ while (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: If controller.[[queueTotalSize]] is 0, return.
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize == 0)
+ return true;
+
+ // Step b: Let pullIntoDescriptor be the first element of
+ // controller.[[pendingPullIntos]].
+ pullIntoDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step c: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller, pullIntoDescriptor)
+ // is true,
+ bool ready;
+ if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
+ pullIntoDescriptor,
+ &ready))
+ {
+ return false;
+ }
+ if (ready) {
+ // Step i: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
+ return false;
+
+ // Step ii: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
+ // pullIntoDescriptor).
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream,
+ pullIntoDescriptor))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Streams spec, 3.12.17. ReadableByteStreamControllerPullInto ( controller, view )
+static MOZ_MUST_USE JSObject*
+ReadableByteStreamControllerPullInto(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<ArrayBufferViewObject*> view)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 2: Let elementSize be 1.
+ uint32_t elementSize = 1;
+
+ RootedObject ctor(cx);
+ // Step 4: If view has a [[TypedArrayName]] internal slot (i.e., it is not a
+ // DataView),
+ if (view->is<TypedArrayObject>()) {
+ JSProtoKey protoKey = StandardProtoKeyOrNull(view);
+ MOZ_ASSERT(protoKey);
+
+ if (!GetBuiltinConstructor(cx, protoKey, &ctor))
+ return nullptr;
+ elementSize = 1 << TypedArrayShift(view->as<TypedArrayObject>().type());
+ } else {
+ // Step 3: Let ctor be %DataView% (reordered).
+ if (!GetBuiltinConstructor(cx, JSProto_DataView, &ctor))
+ return nullptr;
+ }
+
+ // Step 5: Let pullIntoDescriptor be Record {[[buffer]]: view.[[ViewedArrayBuffer]],
+ // [[byteOffset]]: view.[[ByteOffset]],
+ // [[byteLength]]: view.[[ByteLength]],
+ // [[bytesFilled]]: 0,
+ // [[elementSize]]: elementSize,
+ // [[ctor]]: ctor,
+ // [[readerType]]: "byob"}.
+ bool dummy;
+ RootedArrayBufferObject buffer(cx, &JS_GetArrayBufferViewBuffer(cx, view, &dummy)
+ ->as<ArrayBufferObject>());
+ if (!buffer)
+ return nullptr;
+
+ uint32_t byteOffset = JS_GetArrayBufferViewByteOffset(view);
+ uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
+ Rooted<PullIntoDescriptor*> pullIntoDescriptor(cx);
+ pullIntoDescriptor = PullIntoDescriptor::create(cx, buffer, byteOffset, byteLength, 0,
+ elementSize, ctor,
+ ReaderType_BYOB);
+ if (!pullIntoDescriptor)
+ return nullptr;
+
+ // Step 6: If controller.[[pendingPullIntos]] is not empty,
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ if (pendingPullIntos->getDenseInitializedLength() != 0) {
+ // Step a: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return nullptr;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step b: Append pullIntoDescriptor as the last element of
+ // controller.[[pendingPullIntos]].
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+
+ // Step c: Return ! ReadableStreamAddReadIntoRequest(stream).
+ return ReadableStreamAddReadIntoRequest(cx, stream);
+ }
+
+ // Step 7: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: Let emptyView be ! Construct(ctor, pullIntoDescriptor.[[buffer]],
+ // pullIntoDescriptor.[[byteOffset]], 0).
+ FixedConstructArgs<3> args(cx);
+ args[0].setObject(*buffer);
+ args[1].setInt32(byteOffset);
+ args[2].setInt32(0);
+ RootedObject emptyView(cx, JS_New(cx, ctor, args));
+ if (!emptyView)
+ return nullptr;
+
+ // Step b: Return a promise resolved with
+ // ! CreateIterResultObject(emptyView, true).
+ RootedValue val(cx, ObjectValue(*emptyView));
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, true));
+ if (!iterResult)
+ return nullptr;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step 8: If controller.[[queueTotalSize]] > 0,
+ double queueTotalSize = controller->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ if (queueTotalSize > 0) {
+ // Step a: If ! ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(controller,
+ // pullIntoDescriptor)
+ // is true,
+ bool ready;
+ if (!ReadableByteStreamControllerFillPullIntoDescriptorFromQueue(cx, controller,
+ pullIntoDescriptor, &ready))
+ {
+ return nullptr;
+ }
+
+ if (ready) {
+ // Step i: Let filledView be
+ // ! ReadableByteStreamControllerConvertPullIntoDescriptor(pullIntoDescriptor).
+ RootedObject filledView(cx);
+ filledView = ReadableByteStreamControllerConvertPullIntoDescriptor(cx,
+ pullIntoDescriptor);
+ if (!filledView)
+ return nullptr;
+
+ // Step ii: Perform ! ReadableByteStreamControllerHandleQueueDrain(controller).
+ if (!ReadableByteStreamControllerHandleQueueDrain(cx, controller))
+ return nullptr;
+
+ // Step iii: Return a promise resolved with
+ // ! CreateIterResultObject(filledView, false).
+ val = ObjectValue(*filledView);
+ RootedObject iterResult(cx, CreateIterResultObject(cx, val, false));
+ if (!iterResult)
+ return nullptr;
+ val = ObjectValue(*iterResult);
+ return PromiseObject::unforgeableResolve(cx, val);
+ }
+
+ // Step b: If controller.[[closeRequested]] is true,
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ // Step i: Let e be a TypeError exception.
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "read");
+
+ // Not much we can do about uncatchable exceptions, just bail.
+ RootedValue e(cx);
+ if (!GetAndClearException(cx, &e))
+ return nullptr;
+
+ // Step ii: Perform ! ReadableByteStreamControllerError(controller, e).
+ if (!ReadableStreamControllerError(cx, controller, e))
+ return nullptr;
+
+ // Step iii: Return a promise rejected with e.
+ return PromiseObject::unforgeableReject(cx, e);
+ }
+ }
+
+ // Step 9: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return nullptr;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step 10: Append pullIntoDescriptor as the last element of
+ // controller.[[pendingPullIntos]].
+ val = ObjectValue(*pullIntoDescriptor);
+ if (!AppendToList(cx, pendingPullIntos, val))
+ return nullptr;
+
+ // Step 11: Let promise be ! ReadableStreamAddReadIntoRequest(stream).
+ Rooted<PromiseObject*> promise(cx, ReadableStreamAddReadIntoRequest(cx, stream));
+ if (!promise)
+ return nullptr;
+
+ // Step 12: Perform ! ReadableByteStreamControllerCallPullIfNeeded(controller).
+ if (!ReadableStreamControllerCallPullIfNeeded(cx, controller))
+ return nullptr;
+
+ // Step 13: Return promise.
+ return promise;
+}
+
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInternal(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ double bytesWritten);
+
+// Streams spec 3.12.18. ReadableByteStreamControllerRespond( controller, bytesWritten )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespond(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleValue bytesWrittenVal)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let bytesWritten be ? ToNumber(bytesWritten).
+ double bytesWritten;
+ if (!ToNumber(cx, bytesWrittenVal, &bytesWritten))
+ return false;
+
+ // Step 2: If ! IsFiniteNonNegativeNumber(bytesWritten) is false,
+ if (bytesWritten < 0 || mozilla::IsNaN(bytesWritten) || mozilla::IsInfinite(bytesWritten)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "bytesWritten");
+ return false;
+ }
+
+ // Step 3: Assert: controller.[[pendingPullIntos]] is not empty.
+#if DEBUG
+ Value val = controller->getFixedSlot(ByteControllerSlot_PendingPullIntos);
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);
+#endif // DEBUG
+
+ // Step 4: Perform ? ReadableByteStreamControllerRespondInternal(controller, bytesWritten).
+ return ReadableByteStreamControllerRespondInternal(cx, controller, bytesWritten);
+}
+
+// Streams spec 3.12.19. ReadableByteStreamControllerRespondInClosedState( controller, firstDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInClosedState(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ Handle<PullIntoDescriptor*> firstDescriptor)
+{
+ // Step 1: Set firstDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(firstDescriptor.[[buffer]]).
+ RootedArrayBufferObject buffer(cx, firstDescriptor->buffer());
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+ firstDescriptor->setBuffer(transferredBuffer);
+
+ // Step 2: Assert: firstDescriptor.[[bytesFilled]] is 0.
+ MOZ_ASSERT(firstDescriptor->bytesFilled() == 0);
+
+ // Step 3: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 4: If ReadableStreamHasBYOBReader(stream) is true,
+ if (ReadableStreamHasBYOBReader(stream)) {
+ // Step a: Repeat the following steps while
+ // ! ReadableStreamGetNumReadIntoRequests(stream) > 0,
+ Rooted<PullIntoDescriptor*> descriptor(cx);
+ while (ReadableStreamGetNumReadRequests(stream) > 0) {
+ // Step i: Let pullIntoDescriptor be
+ // ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ descriptor = ReadableByteStreamControllerShiftPendingPullInto(cx, controller);
+ if (!descriptor)
+ return false;
+
+ // Step ii: Perform !
+ // ReadableByteStreamControllerCommitPullIntoDescriptor(stream,
+ // pullIntoDescriptor).
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, descriptor))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Streams spec 3.12.20.
+// ReadableByteStreamControllerRespondInReadableState( controller, bytesWritten, pullIntoDescriptor )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInReadableState(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ uint32_t bytesWritten,
+ Handle<PullIntoDescriptor*> pullIntoDescriptor)
+{
+ // Step 1: If pullIntoDescriptor.[[bytesFilled]] + bytesWritten > pullIntoDescriptor.[[byteLength]],
+ // throw a RangeError exception.
+ uint32_t bytesFilled = pullIntoDescriptor->bytesFilled();
+ uint32_t byteLength = pullIntoDescriptor->byteLength();
+ if (bytesFilled + bytesWritten > byteLength) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN);
+ return false;
+ }
+
+ // Step 2: Perform ! ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller,
+ // bytesWritten,
+ // pullIntoDescriptor).
+ ReadableByteStreamControllerFillHeadPullIntoDescriptor(controller, bytesWritten,
+ pullIntoDescriptor);
+ bytesFilled += bytesWritten;
+
+ // Step 3: If pullIntoDescriptor.[[bytesFilled]] <
+ // pullIntoDescriptor.[[elementSize]], return.
+ uint32_t elementSize = pullIntoDescriptor->elementSize();
+ if (bytesFilled < elementSize)
+ return true;
+
+ // Step 4: Perform ! ReadableByteStreamControllerShiftPendingPullInto(controller).
+ if (!ReadableByteStreamControllerShiftPendingPullInto(cx, controller))
+ return false;
+
+ // Step 5: Let remainderSize be pullIntoDescriptor.[[bytesFilled]] mod
+ // pullIntoDescriptor.[[elementSize]].
+ uint32_t remainderSize = bytesFilled % elementSize;
+
+ // Step 6: If remainderSize > 0,
+ RootedArrayBufferObject buffer(cx, pullIntoDescriptor->buffer());
+ if (remainderSize > 0) {
+ // Step a: Let end be pullIntoDescriptor.[[byteOffset]] +
+ // pullIntoDescriptor.[[bytesFilled]].
+ uint32_t end = pullIntoDescriptor->byteOffset() + bytesFilled;
+
+ // Step b: Let remainder be ? CloneArrayBuffer(pullIntoDescriptor.[[buffer]],
+ // end − remainderSize,
+ // remainderSize, %ArrayBuffer%).
+ // TODO: this really, really should just use a slot to store the remainder.
+ RootedObject remainderObj(cx, JS_NewArrayBuffer(cx, remainderSize));
+ if (!remainderObj)
+ return false;
+ RootedArrayBufferObject remainder(cx, &remainderObj->as<ArrayBufferObject>());
+ ArrayBufferObject::copyData(remainder, 0, buffer, end - remainderSize, remainderSize);
+
+ // Step c: Perform ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // remainder, 0,
+ // remainder.[[ByteLength]]).
+ // Note: `remainderSize` is equivalent to remainder.[[ByteLength]].
+ if (!ReadableByteStreamControllerEnqueueChunkToQueue(cx, controller, remainder, 0,
+ remainderSize))
+ {
+ return false;
+ }
+ }
+
+ // Step 7: Set pullIntoDescriptor.[[buffer]] to
+ // ! TransferArrayBuffer(pullIntoDescriptor.[[buffer]]).
+ RootedArrayBufferObject transferredBuffer(cx, TransferArrayBuffer(cx, buffer));
+ if (!transferredBuffer)
+ return false;
+ pullIntoDescriptor->setBuffer(transferredBuffer);
+
+ // Step 8: Set pullIntoDescriptor.[[bytesFilled]] to pullIntoDescriptor.[[bytesFilled]] −
+ // remainderSize.
+ pullIntoDescriptor->setBytesFilled(bytesFilled - remainderSize);
+
+ // Step 9: Perform ! ReadableByteStreamControllerCommitPullIntoDescriptor(controller.[[controlledReadableStream]],
+ // pullIntoDescriptor).
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+ if (!ReadableByteStreamControllerCommitPullIntoDescriptor(cx, stream, pullIntoDescriptor))
+ return false;
+
+ // Step 10: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ return ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller);
+}
+
+// Streams spec, 3.12.21. ReadableByteStreamControllerRespondInternal ( controller, bytesWritten )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondInternal(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ double bytesWritten)
+{
+ // Step 1: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step 2: Let stream be controller.[[controlledReadableStream]].
+ Rooted<ReadableStream*> stream(cx, StreamFromController(controller));
+
+ // Step 3: If stream.[[state]] is "closed",
+ if (stream->closed()) {
+ // Step a: If bytesWritten is not 0, throw a TypeError exception.
+ if (bytesWritten != 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED);
+ return false;
+ }
+
+ // Step b: Perform
+ // ! ReadableByteStreamControllerRespondInClosedState(controller, firstDescriptor).
+ return ReadableByteStreamControllerRespondInClosedState(cx, controller, firstDescriptor);
+ }
+
+ // Step 4: Otherwise,
+ // Step a: Assert: stream.[[state]] is "readable".
+ MOZ_ASSERT(stream->readable());
+
+ // Step b: Perform ? ReadableByteStreamControllerRespondInReadableState(controller,
+ // bytesWritten,
+ // firstDescriptor).
+ return ReadableByteStreamControllerRespondInReadableState(cx, controller, bytesWritten,
+ firstDescriptor);
+}
+
+// Streams spec, 3.12.22. ReadableByteStreamControllerRespondWithNewView ( controller, view )
+static MOZ_MUST_USE bool
+ReadableByteStreamControllerRespondWithNewView(JSContext* cx,
+ Handle<ReadableByteStreamController*> controller,
+ HandleObject view)
+{
+ // Step 1: Assert: controller.[[pendingPullIntos]] is not empty.
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(pendingPullIntos->getDenseInitializedLength() != 0);
+
+ // Step 2: Let firstDescriptor be the first element of controller.[[pendingPullIntos]].
+ Rooted<PullIntoDescriptor*> firstDescriptor(cx);
+ firstDescriptor = PeekList<PullIntoDescriptor>(pendingPullIntos);
+
+ // Step 3: If firstDescriptor.[[byteOffset]] + firstDescriptor.[[bytesFilled]]
+ // is not view.[[ByteOffset]], throw a RangeError exception.
+ uint32_t byteOffset = uint32_t(JS_GetArrayBufferViewByteOffset(view));
+ if (firstDescriptor->byteOffset() + firstDescriptor->bytesFilled() != byteOffset) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET);
+ return false;
+ }
+
+ // Step 4: If firstDescriptor.[[byteLength]] is not view.[[ByteLength]],
+ // throw a RangeError exception.
+ uint32_t byteLength = JS_GetArrayBufferViewByteLength(view);
+ if (firstDescriptor->byteLength() != byteLength) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE);
+ return false;
+ }
+
+ // Step 5: Set firstDescriptor.[[buffer]] to view.[[ViewedArrayBuffer]].
+ bool dummy;
+ RootedArrayBufferObject buffer(cx,
+ &AsArrayBuffer(JS_GetArrayBufferViewBuffer(cx, view, &dummy)));
+ if (!buffer)
+ return false;
+ firstDescriptor->setBuffer(buffer);
+
+ // Step 6: Perform ? ReadableByteStreamControllerRespondInternal(controller,
+ // view.[[ByteLength]]).
+ return ReadableByteStreamControllerRespondInternal(cx, controller, byteLength);
+}
+
+// Streams spec, 3.12.23. ReadableByteStreamControllerShiftPendingPullInto ( controller )
+static MOZ_MUST_USE PullIntoDescriptor*
+ReadableByteStreamControllerShiftPendingPullInto(JSContext* cx, HandleNativeObject controller)
+{
+ MOZ_ASSERT(controller->is<ReadableByteStreamController>());
+
+ // Step 1: Let descriptor be the first element of controller.[[pendingPullIntos]].
+ // Step 2: Remove descriptor from controller.[[pendingPullIntos]], shifting
+ // all other elements downward (so that the second becomes the first,
+ // and so on).
+ RootedValue val(cx, controller->getFixedSlot(ByteControllerSlot_PendingPullIntos));
+ RootedNativeObject pendingPullIntos(cx, &val.toObject().as<NativeObject>());
+ Rooted<PullIntoDescriptor*> descriptor(cx);
+ descriptor = ShiftFromList<PullIntoDescriptor>(cx, pendingPullIntos);
+ MOZ_ASSERT(descriptor);
+
+ // Step 3: Perform ! ReadableByteStreamControllerInvalidateBYOBRequest(controller).
+ ReadableByteStreamControllerInvalidateBYOBRequest(controller);
+
+ // Step 4: Return descriptor.
+ return descriptor;
+}
+
+// Streams spec, 3.12.24. ReadableByteStreamControllerShouldCallPull ( controller )
+// Unified with 3.9.3 above.
+
+// Streams spec, 6.1.2. new ByteLengthQueuingStrategy({ highWaterMark })
+bool
+js::ByteLengthQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ RootedObject strategy(cx, NewBuiltinClassInstance<ByteLengthQueuingStrategy>(cx));
+ if (!strategy)
+ return false;
+
+ RootedObject argObj(cx, ToObject(cx, args.get(0)));
+ if (!argObj)
+ return false;
+
+ RootedValue highWaterMark(cx);
+ if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
+ return false;
+
+ if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
+ return false;
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.1.3.1. size ( chunk )
+bool
+ByteLengthQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return ? GetV(chunk, "byteLength").
+ return GetProperty(cx, args.get(0), cx->names().byteLength, args.rval());
+}
+
+static const JSPropertySpec ByteLengthQueuingStrategy_properties[] = {
+ JS_PS_END
+};
+
+static const JSFunctionSpec ByteLengthQueuingStrategy_methods[] = {
+ JS_FN("size", ByteLengthQueuingStrategy_size, 1, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(ByteLengthQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
+
+// Streams spec, 6.2.2. new CountQueuingStrategy({ highWaterMark })
+bool
+js::CountQueuingStrategy::constructor(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ Rooted<CountQueuingStrategy*> strategy(cx, NewBuiltinClassInstance<CountQueuingStrategy>(cx));
+ if (!strategy)
+ return false;
+
+ RootedObject argObj(cx, ToObject(cx, args.get(0)));
+ if (!argObj)
+ return false;
+
+ RootedValue highWaterMark(cx);
+ if (!GetProperty(cx, argObj, argObj, cx->names().highWaterMark, &highWaterMark))
+ return false;
+
+ if (!SetProperty(cx, strategy, cx->names().highWaterMark, highWaterMark))
+ return false;
+
+ args.rval().setObject(*strategy);
+ return true;
+}
+
+// Streams spec 6.2.3.1. size ( chunk )
+bool
+CountQueuingStrategy_size(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+
+ // Step 1: Return 1.
+ args.rval().setInt32(1);
+ return true;
+}
+
+static const JSPropertySpec CountQueuingStrategy_properties[] = {
+ JS_PS_END
+};
+
+static const JSFunctionSpec CountQueuingStrategy_methods[] = {
+ JS_FN("size", CountQueuingStrategy_size, 0, 0),
+ JS_FS_END
+};
+
+CLASS_SPEC(CountQueuingStrategy, 1, 0, 0, 0, JS_NULL_CLASS_OPS);
+
+#undef CLASS_SPEC
+
+// Streams spec, 6.3.1. DequeueValue ( container ) nothrow
+inline static MOZ_MUST_USE bool
+DequeueValue(JSContext* cx, HandleNativeObject container, MutableHandleValue chunk)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Assert: queue is not empty.
+ RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+ MOZ_ASSERT(queue->getDenseInitializedLength() > 0);
+
+ // Step 3. Let pair be the first element of queue.
+ // Step 4. Remove pair from queue, shifting all other elements downward
+ // (so that the second becomes the first, and so on).
+ Rooted<QueueEntry*> pair(cx, ShiftFromList<QueueEntry>(cx, queue));
+ MOZ_ASSERT(pair);
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] − pair.[[size]].
+ // Step 6: If container.[[queueTotalSize]] < 0, set
+ // container.[[queueTotalSize]] to 0.
+ // (This can occur due to rounding errors.)
+ double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+
+ totalSize -= pair->size();
+ if (totalSize < 0)
+ totalSize = 0;
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize));
+
+ // Step 7: Return pair.[[value]].
+ chunk.set(pair->value());
+ return true;
+}
+
+// Streams spec, 6.3.2. EnqueueValueWithSize ( container, value, size ) throws
+static MOZ_MUST_USE bool
+EnqueueValueWithSize(JSContext* cx, HandleNativeObject container, HandleValue value,
+ HandleValue sizeVal)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Let size be ? ToNumber(size).
+ double size;
+ if (!ToNumber(cx, sizeVal, &size))
+ return false;
+
+ // Step 3: If ! IsFiniteNonNegativeNumber(size) is false, throw a RangeError
+ // exception.
+ if (size < 0 || mozilla::IsNaN(size) || mozilla::IsInfinite(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
+ return false;
+ }
+
+ // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last element
+ // of container.[[queue]].
+ RootedValue val(cx, container->getFixedSlot(QueueContainerSlot_Queue));
+ RootedNativeObject queue(cx, &val.toObject().as<NativeObject>());
+
+ QueueEntry* entry = QueueEntry::create(cx, value, size);
+ if (!entry)
+ return false;
+ val = ObjectValue(*entry);
+ if (!AppendToList(cx, queue, val))
+ return false;
+
+ // Step 5: Set container.[[queueTotalSize]] to
+ // container.[[queueTotalSize]] + size.
+ double totalSize = container->getFixedSlot(QueueContainerSlot_TotalSize).toNumber();
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(totalSize + size));
+
+ return true;
+}
+
+// Streams spec, 6.3.3. PeekQueueValue ( container ) nothrow
+// Used by WritableStream.
+// static MOZ_MUST_USE Value
+// PeekQueueValue(NativeObject* container)
+// {
+// // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+// // slots.
+// MOZ_ASSERT(IsReadableStreamController(container));
+
+// // Step 2: Assert: queue is not empty.
+// Value val = container->getFixedSlot(QueueContainerSlot_Queue);
+// NativeObject* queue = &val.toObject().as<NativeObject>();
+// MOZ_ASSERT(queue->getDenseInitializedLength() > 0);
+
+// // Step 3: Let pair be the first element of container.[[queue]].
+// QueueEntry* pair = PeekList<QueueEntry>(queue);
+
+// // Step 4: Return pair.[[value]].
+// return pair->value();
+// }
+
+/**
+ * Streams spec, 6.3.4. ResetQueue ( container ) nothrow
+ */
+inline static MOZ_MUST_USE bool
+ResetQueue(JSContext* cx, HandleNativeObject container)
+{
+ // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
+ // slots.
+ MOZ_ASSERT(IsReadableStreamController(container));
+
+ // Step 2: Set container.[[queue]] to a new empty List.
+ if (!SetNewList(cx, container, QueueContainerSlot_Queue))
+ return false;
+
+ // Step 3: Set container.[[queueTotalSize]] to 0.
+ container->setFixedSlot(QueueContainerSlot_TotalSize, NumberValue(0));
+
+ return true;
+}
+
+
+/**
+ * Streams spec, 6.4.1. InvokeOrNoop ( O, P, args )
+ */
+inline static MOZ_MUST_USE bool
+InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
+ MutableHandleValue rval)
+{
+ // Step 1: Assert: P is a valid property key (omitted).
+ // Step 2: If args was not passed, let args be a new empty List (omitted).
+ // Step 3: Let method be ? GetV(O, P).
+ RootedValue method(cx);
+ if (!GetProperty(cx, O, P, &method))
+ return false;
+
+ // Step 4: If method is undefined, return.
+ if (method.isUndefined())
+ return true;
+
+ // Step 5: Return ? Call(method, O, args).
+ return Call(cx, method, O, arg, rval);
+}
+
+/**
+ * Streams spec, 6.4.3. PromiseInvokeOrNoop ( O, P, args )
+ * Specialized to one arg, because that's what all stream related callers use.
+ */
+static MOZ_MUST_USE JSObject*
+PromiseInvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg)
+{
+ // Step 1: Assert: O is not undefined.
+ MOZ_ASSERT(!O.isUndefined());
+
+ // Step 2: Assert: ! IsPropertyKey(P) is true (implicit).
+ // Step 3: Assert: args is a List (omitted).
+
+ // Step 4: Let returnValue be InvokeOrNoop(O, P, args).
+ // Step 5: If returnValue is an abrupt completion, return a promise
+ // rejected with returnValue.[[Value]].
+ RootedValue returnValue(cx);
+ if (!InvokeOrNoop(cx, O, P, arg, &returnValue))
+ return PromiseRejectedWithPendingError(cx);
+
+ // Step 6: Otherwise, return a promise resolved with returnValue.[[Value]].
+ return PromiseObject::unforgeableResolve(cx, returnValue);
+}
+
+/**
+ * Streams spec, 6.4.4 TransferArrayBuffer ( O )
+ */
+static MOZ_MUST_USE ArrayBufferObject*
+TransferArrayBuffer(JSContext* cx, HandleObject buffer)
+{
+ // Step 1 (implicit).
+
+ // Step 2.
+ MOZ_ASSERT(buffer->is<ArrayBufferObject>());
+
+ // Step 3.
+ MOZ_ASSERT(!JS_IsDetachedArrayBufferObject(buffer));
+
+ // Step 5 (reordered).
+ uint32_t size = buffer->as<ArrayBufferObject>().byteLength();
+
+ // Steps 4, 6.
+ void* contents = JS_StealArrayBufferContents(cx, buffer);
+ if (!contents)
+ return nullptr;
+ MOZ_ASSERT(JS_IsDetachedArrayBufferObject(buffer));
+
+ // Step 7.
+ RootedObject transferredBuffer(cx, JS_NewArrayBufferWithContents(cx, size, contents));
+ if (!transferredBuffer)
+ return nullptr;
+ return &transferredBuffer->as<ArrayBufferObject>();
+}
+
+// Streams spec, 6.4.5. ValidateAndNormalizeHighWaterMark ( highWaterMark )
+static MOZ_MUST_USE bool
+ValidateAndNormalizeHighWaterMark(JSContext* cx, HandleValue highWaterMarkVal, double* highWaterMark)
+{
+ // Step 1: Set highWaterMark to ? ToNumber(highWaterMark).
+ if (!ToNumber(cx, highWaterMarkVal, highWaterMark))
+ return false;
+
+ // Step 2: If highWaterMark is NaN, throw a TypeError exception.
+ // Step 3: If highWaterMark < 0, throw a RangeError exception.
+ if (mozilla::IsNaN(*highWaterMark) || *highWaterMark < 0) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_STREAM_INVALID_HIGHWATERMARK);
+ return false;
+ }
+
+ // Step 4: Return highWaterMark.
+ return true;
+}
+
+// Streams spec, 6.4.6. ValidateAndNormalizeQueuingStrategy ( size, highWaterMark )
+static MOZ_MUST_USE bool
+ValidateAndNormalizeQueuingStrategy(JSContext* cx, HandleValue size,
+ HandleValue highWaterMarkVal, double* highWaterMark)
+{
+ // Step 1: If size is not undefined and ! IsCallable(size) is false, throw a
+ // TypeError exception.
+ if (!size.isUndefined() && !IsCallable(size)) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_FUNCTION,
+ "ReadableStream argument options.size");
+ return false;
+ }
+
+ // Step 2: Let highWaterMark be ? ValidateAndNormalizeHighWaterMark(highWaterMark).
+ if (!ValidateAndNormalizeHighWaterMark(cx, highWaterMarkVal, highWaterMark))
+ return false;
+
+ // Step 3: Return Record {[[size]]: size, [[highWaterMark]]: highWaterMark}.
+ return true;
+}
+
+MOZ_MUST_USE bool
+js::ReadableStreamReaderCancel(JSContext* cx, HandleObject readerObj, HandleValue reason)
+{
+ MOZ_ASSERT(IsReadableStreamReader(readerObj));
+ RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
+ MOZ_ASSERT(StreamFromReader(reader));
+ return ReadableStreamReaderGenericCancel(cx, reader, reason);
+}
+
+MOZ_MUST_USE bool
+js::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject readerObj)
+{
+ MOZ_ASSERT(IsReadableStreamReader(readerObj));
+ RootedNativeObject reader(cx, &readerObj->as<NativeObject>());
+ MOZ_ASSERT(ReadableStreamGetNumReadRequests(StreamFromReader(reader)) == 0);
+ return ReadableStreamReaderGenericRelease(cx, reader);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::enqueue(JSContext* cx, Handle<ReadableStream*> stream, HandleValue chunk)
+{
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableStreamDefaultController>();
+
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+ MOZ_ASSERT(stream->readable());
+
+ return ReadableStreamDefaultControllerEnqueue(cx, controller, chunk);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<ArrayBufferObject*> chunk)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+
+ MOZ_ASSERT(!(ControllerFlags(controller) & ControllerFlag_CloseRequested));
+ MOZ_ASSERT(stream->readable());
+
+ return ReadableByteStreamControllerEnqueue(cx, controller, chunk);
+}
+
+void
+ReadableStream::desiredSize(bool* hasSize, double* size) const
+{
+ if (errored()) {
+ *hasSize = false;
+ return;
+ }
+
+ *hasSize = true;
+
+ if (closed()) {
+ *size = 0;
+ return;
+ }
+
+ NativeObject* controller = ControllerFromStream(this);
+ *size = ReadableStreamControllerGetDesiredSizeUnchecked(controller);
+}
+
+/*static */ bool
+ReadableStream::getExternalSource(JSContext* cx, Handle<ReadableStream*> stream, void** source)
+{
+ MOZ_ASSERT(stream->mode() == JS::ReadableStreamMode::ExternalSource);
+ if (stream->locked()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_READABLESTREAM_LOCKED);
+ return false;
+ }
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE,
+ "ReadableStreamGetExternalUnderlyingSource");
+ return false;
+ }
+
+ auto controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+ AddControllerFlags(controller, ControllerFlag_SourceLocked);
+ *source = controller->getFixedSlot(ControllerSlot_UnderlyingSource).toPrivate();
+ return true;
+}
+
+void
+ReadableStream::releaseExternalSource()
+{
+ MOZ_ASSERT(mode() == JS::ReadableStreamMode::ExternalSource);
+ MOZ_ASSERT(locked());
+ auto controller = ControllerFromStream(this);
+ MOZ_ASSERT(ControllerFlags(controller) & ControllerFlag_SourceLocked);
+ RemoveControllerFlags(controller, ControllerFlag_SourceLocked);
+}
+
+uint8_t
+ReadableStream::embeddingFlags() const
+{
+ uint8_t flags = ControllerFlags(ControllerFromStream(this)) >> ControllerEmbeddingFlagsOffset;
+ MOZ_ASSERT_IF(flags, mode() == JS::ReadableStreamMode::ExternalSource);
+ return flags;
+}
+
+// Streams spec, 3.10.4.4. steps 1-3
+// and
+// Streams spec, 3.12.8. steps 8-9
+//
+// Adapted to handling updates signaled by the embedding for streams with
+// external underlying sources.
+//
+// The remaining steps of those two functions perform checks and asserts that
+// don't apply to streams with external underlying sources.
+MOZ_MUST_USE bool
+ReadableStream::updateDataAvailableFromSource(JSContext* cx, Handle<ReadableStream*> stream,
+ uint32_t availableData)
+{
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &ControllerFromStream(stream)->as<ReadableByteStreamController>();
+
+ // Step 2: If this.[[closeRequested]] is true, throw a TypeError exception.
+ if (ControllerFlags(controller) & ControllerFlag_CloseRequested) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_CLOSED, "enqueue");
+ return false;
+ }
+
+ // Step 3: If this.[[controlledReadableStream]].[[state]] is not "readable",
+ // throw a TypeError exception.
+ if (!StreamFromController(controller)->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "enqueue");
+ return false;
+ }
+
+ RemoveControllerFlags(controller, ControllerFlag_Pulling | ControllerFlag_PullAgain);
+
+#if DEBUG
+ uint32_t oldAvailableData = controller->getFixedSlot(QueueContainerSlot_TotalSize).toInt32();
+#endif // DEBUG
+ controller->setFixedSlot(QueueContainerSlot_TotalSize, Int32Value(availableData));
+
+ // Step 8.a: If ! ReadableStreamGetNumReadRequests(stream) is 0,
+ // Reordered because for externally-sourced streams it applies regardless
+ // of reader type.
+ if (ReadableStreamGetNumReadRequests(stream) == 0)
+ return true;
+
+ // Step 8: If ! ReadableStreamHasDefaultReader(stream) is true
+ if (ReadableStreamHasDefaultReader(stream)) {
+ // Step b: Otherwise,
+ // Step i: Assert: controller.[[queue]] is empty.
+ MOZ_ASSERT(oldAvailableData == 0);
+
+ // Step ii: Let transferredView be
+ // ! Construct(%Uint8Array%, transferredBuffer, byteOffset, byteLength).
+ JSObject* viewObj = JS_NewUint8Array(cx, availableData);
+ Rooted<ArrayBufferViewObject*> transferredView(cx, &viewObj->as<ArrayBufferViewObject>());
+ if (!transferredView)
+ return false;
+
+ Value val = controller->getFixedSlot(ControllerSlot_UnderlyingSource);
+ void* underlyingSource = val.toPrivate();
+
+ size_t bytesWritten;
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(transferredView, &dummy, noGC);
+ auto cb = cx->runtime()->readableStreamWriteIntoReadRequestCallback;
+ MOZ_ASSERT(cb);
+ // TODO: use bytesWritten to correctly update the request's state.
+ cb(cx, stream, underlyingSource, stream->embeddingFlags(), buffer,
+ availableData, &bytesWritten);
+ }
+
+ // Step iii: Perform ! ReadableStreamFulfillReadRequest(stream, transferredView, false).
+ RootedValue chunk(cx, ObjectValue(*transferredView));
+ if (!ReadableStreamFulfillReadOrReadIntoRequest(cx, stream, chunk, false))
+ return false;
+
+ controller->setFixedSlot(QueueContainerSlot_TotalSize,
+ Int32Value(availableData - bytesWritten));
+ } else if (ReadableStreamHasBYOBReader(stream)) {
+ // Step 9: Otherwise,
+ // Step a: If ! ReadableStreamHasBYOBReader(stream) is true,
+ // Step i: Perform
+ // (Not needed for external underlying sources.)
+
+ // Step ii: Perform ! ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(controller).
+ if (!ReadableByteStreamControllerProcessPullIntoDescriptorsUsingQueue(cx, controller))
+ return false;
+ } else {
+ // Step b: Otherwise,
+ // Step i: Assert: ! IsReadableStreamLocked(stream) is false.
+ MOZ_ASSERT(!stream->locked());
+
+ // Step ii: Perform
+ // ! ReadableByteStreamControllerEnqueueChunkToQueue(controller,
+ // transferredBuffer,
+ // byteOffset,
+ // byteLength).
+ // (Not needed for external underlying sources.)
+ }
+
+ return true;
+}
+
+MOZ_MUST_USE bool
+ReadableStream::close(JSContext* cx, Handle<ReadableStream*> stream)
+{
+ RootedNativeObject controllerObj(cx, ControllerFromStream(stream));
+ if (!VerifyControllerStateForClosing(cx, controllerObj))
+ return false;
+
+ if (controllerObj->is<ReadableStreamDefaultController>()) {
+ Rooted<ReadableStreamDefaultController*> controller(cx);
+ controller = &controllerObj->as<ReadableStreamDefaultController>();
+ return ReadableStreamDefaultControllerClose(cx, controller);
+ }
+
+ Rooted<ReadableByteStreamController*> controller(cx);
+ controller = &controllerObj->as<ReadableByteStreamController>();
+ return ReadableByteStreamControllerClose(cx, controller);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::error(JSContext* cx, Handle<ReadableStream*> stream, HandleValue reason)
+{
+ // Step 3: If stream.[[state]] is not "readable", throw a TypeError exception.
+ if (!stream->readable()) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, "error");
+ return false;
+ }
+
+ // Step 4: Perform ! ReadableStreamDefaultControllerError(this, e).
+ RootedNativeObject controller(cx, ControllerFromStream(stream));
+ return ReadableStreamControllerError(cx, controller, reason);
+}
+
+MOZ_MUST_USE bool
+ReadableStream::tee(JSContext* cx, Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream)
+{
+ return ReadableStreamTee(cx, stream, false, branch1Stream, branch2Stream);
+}
+
+MOZ_MUST_USE NativeObject*
+ReadableStream::getReader(JSContext* cx, Handle<ReadableStream*> stream,
+ JS::ReadableStreamReaderMode mode)
+{
+ if (mode == JS::ReadableStreamReaderMode::Default)
+ return CreateReadableStreamDefaultReader(cx, stream);
+ return CreateReadableStreamBYOBReader(cx, stream);
+}
+
+JS_FRIEND_API(JSObject*)
+js::UnwrapReadableStream(JSObject* obj)
+{
+ if (JSObject* unwrapped = CheckedUnwrap(obj))
+ return unwrapped->is<ReadableStream>() ? unwrapped : nullptr;
+ return nullptr;
+}
diff --git a/js/src/builtin/Stream.h b/js/src/builtin/Stream.h
new file mode 100644
index 0000000000..0726e78361
--- /dev/null
+++ b/js/src/builtin/Stream.h
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef builtin_Stream_h
+#define builtin_Stream_h
+
+#include "builtin/Promise.h"
+#include "vm/NativeObject.h"
+
+
+namespace js {
+
+class AutoSetNewObjectMetadata;
+
+class ReadableStream : public NativeObject
+{
+ public:
+ static ReadableStream* createDefaultStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue size, HandleValue highWaterMark,
+ HandleObject proto = nullptr);
+ static ReadableStream* createByteStream(JSContext* cx, HandleValue underlyingSource,
+ HandleValue highWaterMark,
+ HandleObject proto = nullptr);
+ static ReadableStream* createExternalSourceStream(JSContext* cx, void* underlyingSource,
+ uint8_t flags, HandleObject proto = nullptr);
+
+ bool readable() const;
+ bool closed() const;
+ bool errored() const;
+ bool disturbed() const;
+
+ bool locked() const;
+
+ void desiredSize(bool* hasSize, double* size) const;
+
+ JS::ReadableStreamMode mode() const;
+
+ static MOZ_MUST_USE bool close(JSContext* cx, Handle<ReadableStream*> stream);
+ static MOZ_MUST_USE JSObject* cancel(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue reason);
+ static MOZ_MUST_USE bool error(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue error);
+
+ static MOZ_MUST_USE NativeObject* getReader(JSContext* cx, Handle<ReadableStream*> stream,
+ JS::ReadableStreamReaderMode mode);
+
+ static MOZ_MUST_USE bool tee(JSContext* cx,
+ Handle<ReadableStream*> stream, bool cloneForBranch2,
+ MutableHandle<ReadableStream*> branch1Stream,
+ MutableHandle<ReadableStream*> branch2Stream);
+
+ static MOZ_MUST_USE bool enqueue(JSContext* cx, Handle<ReadableStream*> stream,
+ HandleValue chunk);
+ static MOZ_MUST_USE bool enqueueBuffer(JSContext* cx, Handle<ReadableStream*> stream,
+ Handle<ArrayBufferObject*> chunk);
+ static MOZ_MUST_USE bool getExternalSource(JSContext* cx, Handle<ReadableStream*> stream,
+ void** source);
+ void releaseExternalSource();
+ uint8_t embeddingFlags() const;
+ static MOZ_MUST_USE bool updateDataAvailableFromSource(JSContext* cx,
+ Handle<ReadableStream*> stream,
+ uint32_t availableData);
+
+ enum State {
+ Readable = 1 << 0,
+ Closed = 1 << 1,
+ Errored = 1 << 2,
+ Disturbed = 1 << 3
+ };
+
+ 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_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamDefaultReader : public NativeObject
+{
+ public:
+ static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamDefaultReader*> reader);
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamBYOBReader : public NativeObject
+{
+ public:
+ static MOZ_MUST_USE JSObject* read(JSContext* cx, Handle<ReadableStreamBYOBReader*> reader,
+ Handle<ArrayBufferViewObject*> view);
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+bool ReadableStreamReaderIsClosed(const JSObject* reader);
+
+MOZ_MUST_USE bool ReadableStreamReaderCancel(JSContext* cx, HandleObject reader,
+ HandleValue reason);
+
+MOZ_MUST_USE bool ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader);
+
+class ReadableStreamDefaultController : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableByteStreamController : public NativeObject
+{
+ public:
+ bool hasExternalSource();
+
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ReadableStreamBYOBRequest : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class ByteLengthQueuingStrategy : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+class CountQueuingStrategy : public NativeObject
+{
+ public:
+ static bool constructor(JSContext* cx, unsigned argc, Value* vp);
+ static const ClassSpec classSpec_;
+ static const Class class_;
+ static const ClassSpec protoClassSpec_;
+ static const Class protoClass_;
+};
+
+} // namespace js
+
+#endif /* builtin_Stream_h */
diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp
index 2608733853..f7af8b6fe9 100644
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1441,6 +1441,14 @@ RejectPromise(JSContext* cx, unsigned argc, Value* vp)
return result;
}
+static bool
+StreamsAreEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ args.rval().setBoolean(cx->options().streams());
+ return true;
+}
+
static unsigned finalizeCount = 0;
static void
@@ -4144,6 +4152,10 @@ JS_FN_HELP("rejectPromise", RejectPromise, 2, 0,
"rejectPromise(promise, reason)",
" Reject a Promise by calling the JSAPI function JS::RejectPromise."),
+JS_FN_HELP("streamsAreEnabled", StreamsAreEnabled, 0, 0,
+"streamsAreEnabled()",
+" Returns a boolean indicating whether WHATWG Streams are enabled for the current compartment."),
+
JS_FN_HELP("makeFinalizeObserver", MakeFinalizeObserver, 0, 0,
"makeFinalizeObserver()",
" Get a special object whose finalization increases the counter returned\n"
diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js
index 57f6d738ca..a8510faa71 100644
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -2,8 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include "TypedObjectConstants.h"
-
function ViewedArrayBufferIfReified(tarray) {
assert(IsTypedArray(tarray), "non-typed array asked for its buffer");
@@ -1781,7 +1779,7 @@ function ArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_TYPED_ARRAY_DETACHED);
// Steps 19-21.
- ArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+ ArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 22.
return new_;
@@ -1863,7 +1861,7 @@ function SharedArrayBufferSlice(start, end) {
ThrowTypeError(JSMSG_SHORT_SHARED_ARRAY_BUFFER_RETURNED, newLen, actualLen);
// Steps 16-18.
- SharedArrayBufferCopyData(new_, O, first | 0, newLen | 0, isWrapped);
+ SharedArrayBufferCopyData(new_, 0, O, first | 0, newLen | 0, isWrapped);
// Step 19.
return new_;
diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js
index 51c5a574fd..1cc15ed68e 100644
--- a/js/src/builtin/Utilities.js
+++ b/js/src/builtin/Utilities.js
@@ -23,6 +23,7 @@
*/
#include "SelfHostingDefines.h"
+#include "TypedObjectConstants.h"
// Assertions and debug printing, defined here instead of in the header above
// to make `assert` invisible to C++.
diff --git a/js/src/js.msg b/js/src/js.msg
index e651b10137..b8aa77902d 100644
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -613,11 +613,39 @@ MSG_DEF(JSMSG_RETURN_NOT_CALLABLE, 0, JSEXN_TYPEERR, "property 'return' of i
MSG_DEF(JSMSG_ITERATOR_NO_THROW, 0, JSEXN_TYPEERR, "iterator does not have a 'throw' method")
// Async Iteration
-MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop should be used with 'of'")
+MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_SYNTAXERR, "'for await' loop should be used with 'of'")
MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator")
MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator")
MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value")
+// ReadableStream
+MSG_DEF(JSMSG_READABLESTREAM_UNDERLYINGSOURCE_TYPE_WRONG,0, JSEXN_RANGEERR,"'underlyingSource.type' must be \"bytes\" or undefined.")
+MSG_DEF(JSMSG_READABLESTREAM_INVALID_READER_MODE, 0, JSEXN_RANGEERR,"'mode' must be \"byob\" or undefined.")
+MSG_DEF(JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, 1, JSEXN_RANGEERR, "'{0}' must be a finite, non-negative number.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_BYTESWRITTEN, 0, JSEXN_RANGEERR, "'bytesWritten' exceeds remaining length.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_SIZE, 0, JSEXN_RANGEERR, "view size does not match requested data.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_INVALID_VIEW_OFFSET, 0, JSEXN_RANGEERR, "view offset does not match requested position.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_LOCKED, 1, JSEXN_TYPEERR, "'{0}' may only be called on a locked stream.")
+MSG_DEF(JSMSG_READABLESTREAM_LOCKED, 0, JSEXN_TYPEERR, "A Reader may only be created for an unlocked ReadableStream.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableByteStreamController.")
+MSG_DEF(JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER, 1, JSEXN_TYPEERR, "{0} requires a ReadableStreamDefaultController.")
+MSG_DEF(JSMSG_READABLESTREAM_CONTROLLER_SET, 0, JSEXN_TYPEERR, "The ReadableStream already has a controller defined.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_OWNED, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may only be called on a reader owned by a stream.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_NOT_EMPTY, 1, JSEXN_TYPEERR, "The ReadableStream reader method '{0}' may not be called on a reader with read requests.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREADER_READ_EMPTY_VIEW, 0, JSEXN_TYPEERR, "ReadableStreamBYOBReader.read() was passed an empty TypedArrayBuffer view.")
+MSG_DEF(JSMSG_READABLESTREAMREADER_RELEASED, 0, JSEXN_TYPEERR, "The ReadableStream reader was released.")
+MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_CLOSED, 1, JSEXN_TYPEERR, "'{0}' called on a stream already closing.")
+MSG_DEF(JSMSG_READABLESTREAMCONTROLLER_NOT_READABLE, 1, JSEXN_TYPEERR, "'{0}' may only be called on a stream in the 'readable' state.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNKSIZE,0, JSEXN_RANGEERR, "ReadableByteStreamController requires a positive integer or undefined for 'autoAllocateChunkSize'.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK, 1, JSEXN_TYPEERR, "{0} passed a bad chunk.")
+MSG_DEF(JSMSG_READABLEBYTESTREAMCONTROLLER_CLOSE_PENDING_PULL, 0, JSEXN_TYPEERR, "The ReadableByteStreamController cannot be closed while the buffer is being filled.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_NO_CONTROLLER, 1, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method '{0}' called on a request with no controller.")
+MSG_DEF(JSMSG_READABLESTREAMBYOBREQUEST_RESPOND_CLOSED, 0, JSEXN_TYPEERR, "ReadableStreamBYOBRequest method 'respond' called with non-zero number of bytes with a closed controller.")
+MSG_DEF(JSMSG_READABLESTREAM_METHOD_NOT_IMPLEMENTED, 1, JSEXN_TYPEERR, "ReadableStream method {0} not yet implemented")
+
+// Other Stream-related
+MSG_DEF(JSMSG_STREAM_INVALID_HIGHWATERMARK, 0, JSEXN_RANGEERR, "'highWaterMark' must be a non-negative, non-NaN number.")
+
// BigInt
MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number")
MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt")
diff --git a/js/src/jsapi-tests/moz.build b/js/src/jsapi-tests/moz.build
index 35bc69b85b..1e5f43aa5a 100644
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -126,6 +126,11 @@ if CONFIG['ENABLE_ION']:
'testJitRValueAlloc.cpp',
]
+if CONFIG['ENABLE_STREAMS']:
+ UNIFIED_SOURCES += [
+ 'testReadableStream.cpp',
+ ]
+
DEFINES['EXPORT_JS_API'] = True
LOCAL_INCLUDES += [
diff --git a/js/src/jsapi-tests/testIntTypesABI.cpp b/js/src/jsapi-tests/testIntTypesABI.cpp
index 066e7ad1b8..420a07c7f8 100644
--- a/js/src/jsapi-tests/testIntTypesABI.cpp
+++ b/js/src/jsapi-tests/testIntTypesABI.cpp
@@ -29,6 +29,7 @@
#include "js/RequiredDefines.h"
#include "js/RootingAPI.h"
#include "js/SliceBudget.h"
+#include "js/Stream.h"
#include "js/StructuredClone.h"
#include "js/TracingAPI.h"
#include "js/TrackedOptimizationInfo.h"
diff --git a/js/src/jsapi-tests/testReadableStream.cpp b/js/src/jsapi-tests/testReadableStream.cpp
new file mode 100644
index 0000000000..f1d373d5d2
--- /dev/null
+++ b/js/src/jsapi-tests/testReadableStream.cpp
@@ -0,0 +1,676 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi.h"
+
+#include "jsapi-tests/tests.h"
+
+using namespace JS;
+
+char test_buffer_data[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+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 JSObject*
+NewByteStream(JSContext* cx, double highWaterMark = 0, HandleObject proto = nullptr)
+{
+ RootedObject source(cx, JS_NewPlainObject(cx));
+ MOZ_ASSERT(source);
+
+ RootedObject stream(cx, NewReadableByteStreamObject(cx, source, highWaterMark, proto));
+ MOZ_ASSERT_IF(stream, IsReadableStream(stream));
+ return stream;
+}
+
+static bool dataRequestCBCalled = false;
+static void
+DataRequestCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ size_t desiredSize)
+{
+ MOZ_ASSERT(!dataRequestCBCalled, "Invalid test setup");
+ dataRequestCBCalled = true;
+}
+
+static bool writeIntoRequestBufferCBCalled = false;
+static void
+WriteIntoRequestBufferCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ void* buffer, size_t length, size_t* bytesWritten)
+{
+ MOZ_ASSERT(!writeIntoRequestBufferCBCalled, "Invalid test setup");
+ MOZ_ASSERT(length <= sizeof(test_buffer_data));
+ memcpy(buffer, test_buffer_data, length);
+ writeIntoRequestBufferCBCalled = true;
+ *bytesWritten = length;
+}
+
+static bool cancelStreamCBCalled = false;
+static Value cancelStreamReason;
+static Value
+CancelStreamCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ HandleValue 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, uint8_t flags)
+{
+ MOZ_ASSERT(!streamClosedCBCalled, "Invalid test setup");
+ streamClosedCBCalled = true;
+}
+
+static bool streamErroredCBCalled = false;
+static Value streamErroredReason;
+static void
+StreamErroredCB(JSContext* cx, HandleObject stream, void* underlyingSource, uint8_t flags,
+ HandleValue reason)
+{
+ MOZ_ASSERT(!streamErroredCBCalled, "Invalid test setup");
+ streamErroredCBCalled = true;
+ streamErroredReason = reason;
+}
+
+static bool finalizeStreamCBCalled = false;
+static void* finalizedStreamUnderlyingSource;
+static void
+FinalizeStreamCB(void* underlyingSource, uint8_t flags)
+{
+ 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))
+ return false;
+ MOZ_ASSERT(found);
+ if (!JS_HasProperty(cx, iterResult, "done", &found))
+ return false;
+ MOZ_ASSERT(found);
+
+ RootedValue doneVal(cx);
+ if (!JS_GetProperty(cx, iterResult, "value", value))
+ return false;
+ if (!JS_GetProperty(cx, iterResult, "done", &doneVal))
+ return false;
+
+ *done = doneVal.toBoolean();
+ MOZ_ASSERT_IF(*done, value.isUndefined());
+
+ return true;
+}
+
+static JSObject*
+GetReadChunk(JSContext* cx, HandleObject readRequest)
+{
+ MOZ_ASSERT(GetPromiseState(readRequest) == PromiseState::Fulfilled);
+ RootedValue resultVal(cx, GetPromiseResult(readRequest));
+ MOZ_ASSERT(resultVal.isObject());
+ RootedObject result(cx, &resultVal.toObject());
+ RootedValue chunkVal(cx);
+ JS_GetProperty(cx, result, "value", &chunkVal);
+ return &chunkVal.toObject();
+}
+
+BEGIN_TEST(testReadableStream_NewReadableStream)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Default);
+ return true;
+}
+END_TEST(testReadableStream_NewReadableStream)
+
+BEGIN_TEST(testReadableStream_NewReadableByteStream)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == ReadableStreamMode::Byte);
+ return true;
+}
+END_TEST(testReadableStream_NewReadableByteStream)
+
+BEGIN_TEST(testReadableStream_ReadableStreamGetReaderDefault)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+ CHECK(IsReadableStreamDefaultReader(reader));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(!ReadableStreamReaderIsClosed(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamGetReaderDefault)
+
+BEGIN_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
+ CHECK(reader);
+ CHECK(IsReadableStreamBYOBReader(reader));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(!ReadableStreamReaderIsClosed(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamGetReaderBYOB)
+
+BEGIN_TEST(testReadableStream_ReadableStreamTee)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject leftStream(cx);
+ RootedObject rightStream(cx);
+ CHECK(ReadableStreamTee(cx, stream, &leftStream, &rightStream));
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(leftStream);
+ CHECK(IsReadableStream(leftStream));
+ CHECK(rightStream);
+ CHECK(IsReadableStream(rightStream));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamTee)
+
+BEGIN_TEST(testReadableStream_ReadableStreamEnqueue)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject chunk(cx, JS_NewPlainObject(cx));
+ CHECK(chunk);
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamEnqueue)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamEnqueue)
+{
+ RootedObject stream(cx, NewDefaultStream(cx));
+ CHECK(stream);
+
+ RootedObject chunk(cx, JS_NewUint8Array(cx, 42));
+ CHECK(chunk);
+ CHECK(!ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
+ CHECK(JS_IsExceptionPending(cx));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamEnqueue)
+
+BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
+{
+ 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));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ RootedObject chunk(cx, JS_NewPlainObject(cx));
+ CHECK(chunk);
+ RootedValue chunkVal(cx, ObjectValue(*chunk));
+ CHECK(ReadableStreamEnqueue(cx, stream, chunkVal));
+
+ CHECK(GetReadChunk(cx, request) == chunk);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderRead)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::Default));
+ CHECK(reader);
+
+ RootedObject request(cx, ReadableStreamDefaultReaderRead(cx, reader));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ size_t length = sizeof(test_buffer_data);
+ RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
+ CHECK(buffer);
+ RootedObject chunk(cx, JS_NewUint8ArrayWithBuffer(cx, buffer, 0, length));
+ CHECK(chunk);
+ bool isShared;
+ CHECK(!JS_IsDetachedArrayBufferObject(buffer));
+
+ CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, chunk));
+
+ CHECK(JS_IsDetachedArrayBufferObject(buffer));
+ RootedObject readChunk(cx, GetReadChunk(cx, request));
+ CHECK(JS_IsUint8Array(readChunk));
+ void* readBufferData;
+ {
+ JS::AutoCheckCannotGC autoNoGC(cx);
+ readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
+ }
+ CHECK(readBufferData);
+ CHECK(!memcmp(test_buffer_data, readBufferData, length));
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamDefaultReaderRead)
+
+BEGIN_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
+{
+ RootedObject stream(cx, NewByteStream(cx));
+ CHECK(stream);
+
+ RootedObject reader(cx, ReadableStreamGetReader(cx, stream, ReadableStreamReaderMode::BYOB));
+ CHECK(reader);
+
+ size_t length = sizeof(test_buffer_data);
+ RootedObject targetArray(cx, JS_NewUint8Array(cx, length));
+ bool isShared;
+
+ RootedObject request(cx, ReadableStreamBYOBReaderRead(cx, reader, targetArray));
+ CHECK(request);
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+ CHECK(JS_IsDetachedArrayBufferObject(JS_GetArrayBufferViewBuffer(cx, targetArray, &isShared)));
+
+ RootedObject buffer(cx, JS_NewArrayBufferWithExternalContents(cx, length, test_buffer_data));
+ CHECK(buffer);
+ CHECK(!JS_IsDetachedArrayBufferObject(buffer));
+
+ CHECK(ReadableByteStreamEnqueueBuffer(cx, stream, buffer));
+
+ CHECK(JS_IsDetachedArrayBufferObject(buffer));
+ RootedObject readChunk(cx, GetReadChunk(cx, request));
+ CHECK(JS_IsUint8Array(readChunk));
+ void* readBufferData;
+ {
+ JS::AutoCheckCannotGC autoNoGC(cx);
+ readBufferData = JS_GetArrayBufferViewData(readChunk, &isShared, autoNoGC);
+ }
+ CHECK(readBufferData);
+ CHECK(!memcmp(test_buffer_data, readBufferData, length));
+ // TODO: eliminate the memcpy that happens here.
+// CHECK(readBufferData == test_buffer_data);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableByteStreamBYOBReaderRead)
+
+BEGIN_TEST(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));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(ReadableStreamClose(cx, stream));
+
+ 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);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderClose)
+
+BEGIN_TEST(testReadableStream_ReadableStreamDefaultReaderError)
+{
+ ResetCallbacks();
+ 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));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(ReadableStreamIsLocked(stream));
+ CHECK(ReadableStreamIsReadable(stream));
+ RootedValue error(cx, Int32Value(42));
+ 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);
+
+ return true;
+}
+END_TEST(testReadableStream_ReadableStreamDefaultReaderError)
+
+static JSObject*
+NewExternalSourceStream(JSContext* cx, void* underlyingSource,
+ RequestReadableStreamDataCallback dataRequestCallback,
+ WriteIntoReadRequestBufferCallback writeIntoReadRequestCallback,
+ CancelReadableStreamCallback cancelCallback,
+ ReadableStreamClosedCallback closedCallback,
+ ReadableStreamErroredCallback erroredCallback,
+ ReadableStreamFinalizeCallback finalizeCallback)
+{
+ SetReadableStreamCallbacks(cx, dataRequestCallback, writeIntoReadRequestCallback,
+ cancelCallback, closedCallback, erroredCallback,
+ finalizeCallback);
+ RootedObject stream(cx, NewReadableExternalSourceStreamObject(cx, underlyingSource));
+ MOZ_ASSERT_IF(stream, IsReadableStream(stream));
+ return stream;
+}
+
+BEGIN_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ CHECK(ReadableStreamGetMode(stream) == JS::ReadableStreamMode::ExternalSource);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ return true;
+}
+END_TEST(testReadableStream_CreateReadableByteStreamWithExternalSource)
+
+BEGIN_TEST(testReadableStream_ExternalSourceCancel)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ RootedValue reason(cx, Int32Value(42));
+ CHECK(ReadableStreamCancel(cx, stream, reason));
+ CHECK(cancelStreamCBCalled);
+ CHECK(cancelStreamReason == reason);
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceCancel)
+
+BEGIN_TEST(testReadableStream_ExternalSourceGetReader)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+ RootedValue rval(cx);
+ EVAL("stream.getReader()", &rval);
+ CHECK(rval.isObject());
+ RootedObject reader(cx, &rval.toObject());
+ CHECK(IsReadableStreamDefaultReader(reader));
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceGetReader)
+
+BEGIN_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
+{
+ ResetCallbacks();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, 1024);
+
+ return true;
+}
+END_TEST(testReadableStream_ExternalSourceUpdateAvailableData)
+
+struct ReadFromExternalSourceFixture : public JSAPITest
+{
+ virtual ~ReadFromExternalSourceFixture() {}
+
+ bool readWithoutDataAvailable(const char* evalSrc, const char* evalSrc2,
+ uint32_t writtenLength)
+ {
+ ResetCallbacks();
+ definePrint();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB,
+ &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ js::RunJobs(cx);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+
+ RootedValue rval(cx);
+ EVAL(evalSrc, &rval);
+ CHECK(dataRequestCBCalled);
+ CHECK(!writeIntoRequestBufferCBCalled);
+ CHECK(rval.isObject());
+ RootedObject promise(cx, &rval.toObject());
+ CHECK(IsPromiseObject(promise));
+ CHECK(GetPromiseState(promise) == PromiseState::Pending);
+
+ size_t length = sizeof(test_buffer_data);
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
+
+ CHECK(writeIntoRequestBufferCBCalled);
+ CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
+ RootedValue iterVal(cx);
+ bool done;
+ if (!GetIterResult(cx, promise, &iterVal, &done))
+ return false;
+
+ CHECK(!done);
+ RootedObject chunk(cx, &iterVal.toObject());
+ CHECK(JS_IsUint8Array(chunk));
+
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
+ CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
+ }
+
+ dataRequestCBCalled = false;
+ writeIntoRequestBufferCBCalled = false;
+ EVAL(evalSrc2, &rval);
+ CHECK(dataRequestCBCalled);
+ CHECK(!writeIntoRequestBufferCBCalled);
+
+ return true;
+ }
+
+ bool readWithDataAvailable(const char* evalSrc, uint32_t writtenLength) {
+ ResetCallbacks();
+ definePrint();
+
+ RootedObject stream(cx, NewExternalSourceStream(cx, &test_buffer_data, &DataRequestCB,
+ &WriteIntoRequestBufferCB, &CancelStreamCB,
+ &StreamClosedCB, &StreamErroredCB,
+ &FinalizeStreamCB));
+ CHECK(stream);
+ void* underlyingSource;
+ CHECK(ReadableStreamGetExternalUnderlyingSource(cx, stream, &underlyingSource));
+ CHECK(underlyingSource == &test_buffer_data);
+ CHECK(ReadableStreamIsLocked(stream));
+ ReadableStreamReleaseExternalUnderlyingSource(stream);
+
+ size_t length = sizeof(test_buffer_data);
+ ReadableStreamUpdateDataAvailableFromSource(cx, stream, length);
+
+ RootedValue streamVal(cx, ObjectValue(*stream));
+ CHECK(JS_SetProperty(cx, global, "stream", streamVal));
+
+ RootedValue rval(cx);
+ EVAL(evalSrc, &rval);
+ CHECK(writeIntoRequestBufferCBCalled);
+ CHECK(rval.isObject());
+ RootedObject promise(cx, &rval.toObject());
+ CHECK(IsPromiseObject(promise));
+ CHECK(GetPromiseState(promise) == PromiseState::Fulfilled);
+ RootedValue iterVal(cx);
+ bool done;
+ if (!GetIterResult(cx, promise, &iterVal, &done))
+ return false;
+
+ CHECK(!done);
+ RootedObject chunk(cx, &iterVal.toObject());
+ CHECK(JS_IsUint8Array(chunk));
+
+ {
+ JS::AutoCheckCannotGC noGC(cx);
+ bool dummy;
+ void* buffer = JS_GetArrayBufferViewData(chunk, &dummy, noGC);
+ CHECK(!memcmp(buffer, test_buffer_data, writtenLength));
+ }
+
+ return true;
+ }
+};
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
+{
+ return readWithoutDataAvailable("r = stream.getReader(); r.read()", "r.read()", sizeof(test_buffer_data));
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithoutDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceCloseWithPendingRead)
+{
+ CHECK(readWithoutDataAvailable("r = stream.getReader(); request0 = r.read(); "
+ "request1 = r.read(); request0", "r.read()",
+ sizeof(test_buffer_data)));
+
+ RootedValue val(cx);
+ CHECK(JS_GetProperty(cx, global, "request1", &val));
+ CHECK(val.isObject());
+ RootedObject request(cx, &val.toObject());
+ CHECK(IsPromiseObject(request));
+ CHECK(GetPromiseState(request) == PromiseState::Pending);
+
+ CHECK(JS_GetProperty(cx, global, "stream", &val));
+ RootedObject stream(cx, &val.toObject());
+ ReadableStreamClose(cx, stream);
+
+ val = GetPromiseResult(request);
+ MOZ_ASSERT(val.isObject());
+ RootedObject result(cx, &val.toObject());
+
+ JS_GetProperty(cx, result, "done", &val);
+ CHECK(val.isBoolean());
+ CHECK(val.toBoolean() == true);
+
+ JS_GetProperty(cx, result, "value", &val);
+ CHECK(val.isUndefined());
+ return true;
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceCloseWithPendingRead)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
+{
+ return readWithDataAvailable("r = stream.getReader(); r.read()", sizeof(test_buffer_data));
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadDefaultWithDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
+{
+ return readWithoutDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(63))", "r.read(new Uint8Array(10))", 10);
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithoutDataAvailable)
+
+BEGIN_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithDataAvailable)
+{
+ return readWithDataAvailable("r = stream.getReader({mode: 'byob'}); r.read(new Uint8Array(10))", 10);
+}
+END_FIXTURE_TEST(ReadFromExternalSourceFixture,
+ testReadableStream_ExternalSourceReadBYOBWithDataAvailable)
diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp
index b75fa6602d..61e79eab6d 100644
--- a/js/src/jsapi-tests/tests.cpp
+++ b/js/src/jsapi-tests/tests.cpp
@@ -81,6 +81,10 @@ JSObject* JSAPITest::createGlobal(JSPrincipals* principals)
/* Create the global object. */
JS::RootedObject newGlobal(cx);
JS::CompartmentOptions options;
+#ifdef ENABLE_STREAMS
+ options.creationOptions().setStreamsEnabled(true);
+#endif
+ printf("enabled\n");
options.behaviors().setVersion(JSVERSION_LATEST);
newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), principals, JS::FireOnNewGlobalHook,
options);
diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp
index 0fd82f7c2d..9067ca3d3d 100644
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -46,6 +46,7 @@
#include "builtin/MapObject.h"
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
+#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#ifdef ENABLE_BINARYDATA
# include "builtin/TypedObject.h"
@@ -5095,7 +5096,6 @@ CallOriginalPromiseThenImpl(JSContext* cx, JS::HandleObject promiseObj,
return false;
}
return true;
-
}
JS_PUBLIC_API(JSObject*)
@@ -5126,7 +5126,7 @@ JS::AddPromiseReactions(JSContext* cx, JS::HandleObject promiseObj,
* Unforgeable version of Promise.all for internal use.
*
* Takes a dense array of Promise objects and returns a promise that's
- * resolved with an array of resolution values when all those promises ahve
+ * resolved with an array of resolution values when all those promises have
* been resolved, or rejected with the rejection value of the first rejected
* promise.
*
@@ -5141,6 +5141,368 @@ JS::GetWaitForAllPromise(JSContext* cx, const JS::AutoObjectVector& promises)
return js::GetWaitForAllPromise(cx, promises);
}
+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->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+ RootedObject source(cx, underlyingSource);
+ if (!source) {
+ source = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!source)
+ return nullptr;
+ }
+ 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::NewReadableByteStreamObject(JSContext* cx,
+ JS::HandleObject underlyingSource /* = nullptr */,
+ double highWaterMark /* = 1 */,
+ JS::HandleObject proto /* = nullptr */)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+ RootedObject source(cx, underlyingSource);
+ if (!source) {
+ source = NewBuiltinClassInstance<PlainObject>(cx);
+ if (!source)
+ return nullptr;
+ }
+ RootedValue sourceVal(cx, ObjectValue(*source));
+ RootedValue highWaterMarkVal(cx, NumberValue(highWaterMark));
+ return ReadableStream::createByteStream(cx, sourceVal, highWaterMarkVal, proto);
+}
+
+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::NewReadableExternalSourceStreamObject(JSContext* cx, void* underlyingSource,
+ uint8_t flags /* = 0 */,
+ HandleObject proto /* = nullptr */)
+{
+ MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+
+#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, flags, proto);
+}
+
+JS_PUBLIC_API(uint8_t)
+JS::ReadableStreamGetEmbeddingFlags(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().embeddingFlags();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStream(const JSObject* obj)
+{
+ return obj->is<ReadableStream>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamDefaultReader>() || obj->is<ReadableStreamBYOBReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamDefaultReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamDefaultReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::IsReadableStreamBYOBReader(const JSObject* obj)
+{
+ return obj->is<ReadableStreamBYOBReader>();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsReadable(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().readable();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsLocked(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().locked();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamIsDisturbed(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().disturbed();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamCancel(JSContext* cx, HandleObject streamObj, HandleValue reason)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, reason);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::cancel(cx, stream, reason);
+}
+
+JS_PUBLIC_API(JS::ReadableStreamMode)
+JS::ReadableStreamGetMode(const JSObject* stream)
+{
+ return stream->as<ReadableStream>().mode();
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamGetReader(JSContext* cx, HandleObject streamObj, ReadableStreamReaderMode mode)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::getReader(cx, stream, mode);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamGetExternalUnderlyingSource(JSContext* cx, HandleObject streamObj, void** source)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::getExternalSource(cx, stream, source);
+}
+
+JS_PUBLIC_API(void)
+JS::ReadableStreamReleaseExternalUnderlyingSource(JSObject* stream)
+{
+ stream->as<ReadableStream>().releaseExternalSource();
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamUpdateDataAvailableFromSource(JSContext* cx, JS::HandleObject streamObj,
+ uint32_t availableData)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::updateDataAvailableFromSource(cx, stream, availableData);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamTee(JSContext* cx, HandleObject streamObj,
+ MutableHandleObject branch1Obj, MutableHandleObject branch2Obj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ Rooted<ReadableStream*> branch1Stream(cx);
+ Rooted<ReadableStream*> branch2Stream(cx);
+
+ if (!ReadableStream::tee(cx, stream, false, &branch1Stream, &branch2Stream))
+ return false;
+
+ branch1Obj.set(branch1Stream);
+ branch2Obj.set(branch2Stream);
+
+ return true;
+}
+
+JS_PUBLIC_API(void)
+JS::ReadableStreamGetDesiredSize(JSObject* streamObj, bool* hasValue, double* value)
+{
+ streamObj->as<ReadableStream>().desiredSize(hasValue, value);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamClose(JSContext* cx, HandleObject streamObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return ReadableStream::close(cx, stream);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamEnqueue(JSContext* cx, HandleObject streamObj, HandleValue chunk)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, chunk);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ if (stream->mode() != JS::ReadableStreamMode::Default) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_DEFAULT_CONTROLLER,
+ "JS::ReadableStreamEnqueue");
+ return false;
+ }
+ return ReadableStream::enqueue(cx, stream, chunk);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableByteStreamEnqueueBuffer(JSContext* cx, HandleObject streamObj, HandleObject chunkObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, chunkObj);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ if (stream->mode() != JS::ReadableStreamMode::Byte) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLESTREAM_NOT_BYTE_STREAM_CONTROLLER,
+ "JS::ReadableByteStreamEnqueueBuffer");
+ return false;
+ }
+
+ Rooted<ArrayBufferObject*> buffer(cx);
+ if (chunkObj->is<ArrayBufferViewObject>()) {
+ bool dummy;
+ buffer = &JS_GetArrayBufferViewBuffer(cx, chunkObj, &dummy)->as<ArrayBufferObject>();
+ } else if (chunkObj->is<ArrayBufferObject>()) {
+ buffer = &chunkObj->as<ArrayBufferObject>();
+ } else {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+ JSMSG_READABLEBYTESTREAMCONTROLLER_BAD_CHUNK,
+ "JS::ReadableByteStreamEnqueueBuffer");
+ return false;
+ }
+
+ return ReadableStream::enqueueBuffer(cx, stream, buffer);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamError(JSContext* cx, HandleObject streamObj, HandleValue error)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, streamObj);
+ assertSameCompartment(cx, error);
+
+ Rooted<ReadableStream*> stream(cx, &streamObj->as<ReadableStream>());
+ return js::ReadableStream::error(cx, stream, error);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderIsClosed(const JSObject* reader)
+{
+ return js::ReadableStreamReaderIsClosed(reader);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderCancel(JSContext* cx, HandleObject reader, HandleValue reason)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, reader);
+ assertSameCompartment(cx, reason);
+
+ return js::ReadableStreamReaderCancel(cx, reader, reason);
+}
+
+JS_PUBLIC_API(bool)
+JS::ReadableStreamReaderReleaseLock(JSContext* cx, HandleObject reader)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, reader);
+
+ return js::ReadableStreamReaderReleaseLock(cx, reader);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamDefaultReaderRead(JSContext* cx, HandleObject readerObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, readerObj);
+
+ Rooted<ReadableStreamDefaultReader*> reader(cx, &readerObj->as<ReadableStreamDefaultReader>());
+ return js::ReadableStreamDefaultReader::read(cx, reader);
+}
+
+JS_PUBLIC_API(JSObject*)
+JS::ReadableStreamBYOBReaderRead(JSContext* cx, HandleObject readerObj, HandleObject viewObj)
+{
+ AssertHeapIsIdle(cx);
+ CHECK_REQUEST(cx);
+ assertSameCompartment(cx, readerObj);
+ assertSameCompartment(cx, viewObj);
+
+ Rooted<ReadableStreamBYOBReader*> reader(cx, &readerObj->as<ReadableStreamBYOBReader>());
+ Rooted<ArrayBufferViewObject*> view(cx, &viewObj->as<ArrayBufferViewObject>());
+ return js::ReadableStreamBYOBReader::read(cx, reader, view);
+}
+
JS_PUBLIC_API(void)
JS::SetAsyncTaskCallbacks(JSContext* cx, JS::StartAsyncTaskCallback start,
JS::FinishAsyncTaskCallback finish)
diff --git a/js/src/jsapi.h b/js/src/jsapi.h
index f80d2602e6..951b612502 100644
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -35,6 +35,7 @@
#include "js/Principals.h"
#include "js/Realm.h"
#include "js/RootingAPI.h"
+#include "js/Stream.h"
#include "js/TracingAPI.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
@@ -1161,6 +1162,16 @@ class JS_PUBLIC_API(ContextOptions) {
return *this;
}
+ bool streams() const { return streams_; }
+ ContextOptions& setStreams(bool flag) {
+ streams_ = flag;
+ return *this;
+ }
+ ContextOptions& toggleStreams() {
+ streams_ = !streams_;
+ return *this;
+ }
+
bool nativeRegExp() const { return nativeRegExp_; }
ContextOptions& setNativeRegExp(bool flag) {
nativeRegExp_ = flag;
@@ -1243,6 +1254,7 @@ class JS_PUBLIC_API(ContextOptions) {
bool strictMode_ : 1;
bool extraWarnings_ : 1;
bool arrayProtoValues_ : 1;
+ bool streams_ : 1;
};
JS_PUBLIC_API(ContextOptions&)
diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h
index b4c4e15801..c463019dac 100644
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1787,6 +1787,9 @@ UnwrapArrayBufferView(JSObject* obj);
extern JS_FRIEND_API(JSObject*)
UnwrapSharedArrayBuffer(JSObject* obj);
+extern JS_FRIEND_API(JSObject*)
+UnwrapReadableStream(JSObject* obj);
+
namespace detail {
@@ -2005,6 +2008,12 @@ JS_IsArrayBufferViewObject(JSObject* obj);
extern JS_FRIEND_API(uint32_t)
JS_GetArrayBufferViewByteLength(JSObject* obj);
+/**
+ * More generic name for JS_GetTypedArrayByteOffset to cover DataViews as well
+ */
+extern JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferViewByteOffset(JSObject* obj);
+
/*
* Return a pointer to the start of the data referenced by a typed array. The
* data is still owned by the typed array, and should not be modified on
diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp
index fd23e6ccd5..beff701bd0 100644
--- a/js/src/jsnum.cpp
+++ b/js/src/jsnum.cpp
@@ -1003,6 +1003,13 @@ static const JSFunctionSpec number_methods[] = {
JS_FS_END
};
+bool
+js::IsInteger(const Value& val)
+{
+ return val.isInt32() ||
+ (mozilla::IsFinite(val.toDouble()) && JS::ToInteger(val.toDouble()) == val.toDouble());
+}
+
// ES6 draft ES6 15.7.3.12
static bool
Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
@@ -1013,9 +1020,7 @@ Number_isInteger(JSContext* cx, unsigned argc, Value* vp)
return true;
}
Value val = args[0];
- args.rval().setBoolean(val.isInt32() ||
- (mozilla::IsFinite(val.toDouble()) &&
- JS::ToInteger(val.toDouble()) == val.toDouble()));
+ args.rval().setBoolean(js::IsInteger(val));
return true;
}
diff --git a/js/src/jsnum.h b/js/src/jsnum.h
index ee07d0a35d..7c96bfb652 100644
--- a/js/src/jsnum.h
+++ b/js/src/jsnum.h
@@ -72,6 +72,10 @@ Int32ToString(ExclusiveContext* cx, int32_t i);
extern JSAtom*
Int32ToAtom(ExclusiveContext* cx, int32_t si);
+// ES6 15.7.3.12
+extern bool
+IsInteger(const Value& val);
+
/*
* Convert an integer or double (contained in the given value) to a string and
* append to the given buffer.
diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h
index 52d0d8ea4a..9822a47b6c 100644
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -108,6 +108,17 @@ IF_SAB(real,imaginary)(Atomics, InitAtomicsClass, OCLASP(Atomics)) \
imaginary(WasmMemory, dummy, dummy) \
imaginary(WasmTable, dummy, dummy) \
real(Promise, InitViaClassSpec, OCLASP(Promise)) \
+ real(ReadableStream, InitViaClassSpec, &js::ReadableStream::class_) \
+ real(ReadableStreamDefaultReader, InitViaClassSpec, &js::ReadableStreamDefaultReader::class_) \
+ real(ReadableStreamBYOBReader, InitViaClassSpec, &js::ReadableStreamBYOBReader::class_) \
+ real(ReadableStreamDefaultController, InitViaClassSpec, &js::ReadableStreamDefaultController::class_) \
+ real(ReadableByteStreamController, InitViaClassSpec, &js::ReadableByteStreamController::class_) \
+ real(ReadableStreamBYOBRequest, InitViaClassSpec, &js::ReadableStreamBYOBRequest::class_) \
+ imaginary(WritableStream, dummy, dummy) \
+ imaginary(WritableStreamDefaultWriter, dummy, dummy) \
+ imaginary(WritableStreamDefaultController,dummy, dummy) \
+ real(ByteLengthQueuingStrategy, InitViaClassSpec, &js::ByteLengthQueuingStrategy::class_) \
+ real(CountQueuingStrategy, InitViaClassSpec, &js::CountQueuingStrategy::class_) \
#define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
diff --git a/js/src/moz.build b/js/src/moz.build
index 5429888a2c..d42cf59730 100644
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -92,6 +92,7 @@ EXPORTS.js += [
'../public/Result.h',
'../public/RootingAPI.h',
'../public/SliceBudget.h',
+ '../public/Stream.h',
'../public/StructuredClone.h',
'../public/SweepingAPI.h',
'../public/TraceKind.h',
@@ -384,6 +385,7 @@ main_deunified_sources = [
SOURCES += [
'builtin/BigInt.cpp',
'builtin/RegExp.cpp',
+ 'builtin/Stream.cpp',
'frontend/Parser.cpp',
'gc/StoreBuffer.cpp',
'jsarray.cpp',
diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp
index a77e128a52..1f89e38482 100644
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -318,6 +318,7 @@ static bool enableUnboxedArrays = false;
static bool enableSharedMemory = SHARED_MEMORY_DEFAULT;
static bool enableWasmAlwaysBaseline = false;
static bool enableArrayProtoValues = true;
+static bool enableStreams = false;
static bool printTiming = false;
static const char* jsCacheDir = nullptr;
static const char* jsCacheAsmJSPath = nullptr;
@@ -7565,6 +7566,7 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
enableUnboxedArrays = op.getBoolOption("unboxed-arrays");
enableWasmAlwaysBaseline = op.getBoolOption("wasm-always-baseline");
enableArrayProtoValues = !op.getBoolOption("no-array-proto-values");
+ enableStreams = op.getBoolOption("enable-streams");
JS::ContextOptionsRef(cx).setBaseline(enableBaseline)
.setIon(enableIon)
@@ -7573,7 +7575,8 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
- .setArrayProtoValues(enableArrayProtoValues);
+ .setArrayProtoValues(enableArrayProtoValues)
+ .setStreams(enableStreams);
if (op.getBoolOption("wasm-check-bce"))
jit::JitOptions.wasmAlwaysCheckBounds = true;
@@ -7845,7 +7848,8 @@ SetWorkerContextOptions(JSContext* cx)
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
.setNativeRegExp(enableNativeRegExp)
.setUnboxedArrays(enableUnboxedArrays)
- .setArrayProtoValues(enableArrayProtoValues);
+ .setArrayProtoValues(enableArrayProtoValues)
+ .setStreams(enableStreams);
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
@@ -8019,6 +8023,7 @@ main(int argc, char** argv, char** envp)
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
+ || !op.addBoolOption('\0', "enable-streams", "Enable WHATWG Streams")
#ifdef ENABLE_SHARED_ARRAY_BUFFER
|| !op.addStringOption('\0', "shared-memory", "on/off",
"SharedArrayBuffer and Atomics "
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
index db1d7c798f..6ace034adb 100644
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -1255,15 +1255,16 @@ ArrayBufferObject::finalize(FreeOp* fop, JSObject* obj)
}
/* static */ void
-ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer,
- Handle<ArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count)
+ArrayBufferObject::copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
+ MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
- memcpy(toBuffer->dataPointer(), fromBuffer->dataPointer() + fromIndex, count);
+ memcpy(toBuffer->dataPointer() + toIndex, fromBuffer->dataPointer() + fromIndex, count);
}
/* static */ void
@@ -1890,6 +1891,17 @@ JS_GetArrayBufferViewByteLength(JSObject* obj)
: obj->as<TypedArrayObject>().byteLength();
}
+JS_FRIEND_API(uint32_t)
+JS_GetArrayBufferViewByteOffset(JSObject* obj)
+{
+ obj = CheckedUnwrap(obj);
+ if (!obj)
+ return 0;
+ return obj->is<DataViewObject>()
+ ? obj->as<DataViewObject>().byteOffset()
+ : obj->as<TypedArrayObject>().byteOffset();
+}
+
JS_FRIEND_API(JSObject*)
JS_GetObjectAsArrayBufferView(JSObject* obj, uint32_t* length, bool* isSharedMemory, uint8_t** data)
{
diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h
index 4ff7962cfb..f4010c6c77 100644
--- a/js/src/vm/ArrayBufferObject.h
+++ b/js/src/vm/ArrayBufferObject.h
@@ -261,9 +261,9 @@ class ArrayBufferObject : public ArrayBufferObjectMaybeShared
template<typename T>
static bool createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Value* vp);
- static void copyData(Handle<ArrayBufferObject*> toBuffer,
- Handle<ArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count);
+ static void copyData(Handle<ArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<ArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count);
static void trace(JSTracer* trc, JSObject* obj);
static void objectMoved(JSObject* obj, const JSObject* old);
diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h
index f05a4db9be..ebfad89fa0 100644
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -18,6 +18,8 @@
macro(apply, apply, "apply") \
macro(args, args, "args") \
macro(arguments, arguments, "arguments") \
+ macro(AcquireReadableStreamBYOBReader, AcquireReadableStreamBYOBReader, "AcquireReadableStreamBYOBReader") \
+ macro(AcquireReadableStreamDefaultReader, AcquireReadableStreamDefaultReader, "AcquireReadableStreamDefaultReader") \
macro(ArrayBufferSpecies, ArrayBufferSpecies, "ArrayBufferSpecies") \
macro(ArrayIterator, ArrayIterator, "Array Iterator") \
macro(ArrayIteratorNext, ArrayIteratorNext, "ArrayIteratorNext") \
@@ -35,6 +37,7 @@
macro(AsyncGeneratorFunction, AsyncGeneratorFunction, "AsyncGeneratorFunction") \
macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \
macro(async, async, "async") \
+ macro(autoAllocateChunkSize, autoAllocateChunkSize, "autoAllocateChunkSize") \
macro(await, await, "await") \
macro(bigint64, bigint64, "bigint64") \
macro(biguint64, biguint64, "biguint64") \
@@ -48,6 +51,7 @@
macro(buffer, buffer, "buffer") \
macro(builder, builder, "builder") \
macro(by, by, "by") \
+ macro(byob, byob, "byob") \
macro(byteAlignment, byteAlignment, "byteAlignment") \
macro(byteLength, byteLength, "byteLength") \
macro(byteOffset, byteOffset, "byteOffset") \
@@ -59,6 +63,7 @@
macro(callee, callee, "callee") \
macro(caller, caller, "caller") \
macro(callFunction, callFunction, "callFunction") \
+ macro(cancel, cancel, "cancel") \
macro(case, case_, "case") \
macro(caseFirst, caseFirst, "caseFirst") \
macro(catch, catch_, "catch") \
@@ -180,6 +185,7 @@
macro(has, has, "has") \
macro(hasOwn, hasOwn, "hasOwn") \
macro(hasOwnProperty, hasOwnProperty, "hasOwnProperty") \
+ macro(highWaterMark, highWaterMark, "highWaterMark") \
macro(hour, hour, "hour") \
macro(hourCycle, hourCycle, "hourCycle") \
macro(if, if_, "if") \
@@ -250,6 +256,7 @@
macro(minusSign, minusSign, "minusSign") \
macro(minute, minute, "minute") \
macro(missingArguments, missingArguments, "missingArguments") \
+ macro(mode, mode, "mode") \
macro(module, module, "module") \
macro(Module, Module, "Module") \
macro(ModuleInstantiate, ModuleInstantiate, "ModuleInstantiate") \
@@ -312,7 +319,52 @@
macro(prototype, prototype, "prototype") \
macro(proxy, proxy, "proxy") \
macro(public, public_, "public") \
+ macro(pull, pull, "pull") \
macro(raw, raw, "raw") \
+ macro(ReadableByteStreamControllerGetDesiredSize, \
+ ReadableByteStreamControllerGetDesiredSize, \
+ "ReadableByteStreamControllerGetDesiredSize") \
+ macro(ReadableByteStreamController_close, \
+ ReadableByteStreamController_close, \
+ "ReadableByteStreamController_close") \
+ macro(ReadableByteStreamController_enqueue, \
+ ReadableByteStreamController_enqueue, \
+ "ReadableByteStreamController_enqueue") \
+ macro(ReadableByteStreamController_error, \
+ ReadableByteStreamController_error, \
+ "ReadableByteStreamController_error") \
+ macro(ReadableStreamBYOBReader_cancel, \
+ ReadableStreamBYOBReader_cancel, \
+ "ReadableStreamBYOBReader_cancel") \
+ macro(ReadableStreamBYOBReader_read, \
+ ReadableStreamBYOBReader_read, \
+ "ReadableStreamBYOBReader_read") \
+ macro(ReadableStreamBYOBReader_releaseLock, \
+ ReadableStreamBYOBReader_releaseLock, \
+ "ReadableStreamBYOBReader_releaseLock") \
+ macro(ReadableStream_cancel, ReadableStream_cancel, "ReadableStream_cancel") \
+ macro(ReadableStreamDefaultControllerGetDesiredSize, \
+ ReadableStreamDefaultControllerGetDesiredSize, \
+ "ReadableStreamDefaultControllerGetDesiredSize") \
+ macro(ReadableStreamDefaultController_close, \
+ ReadableStreamDefaultController_close, \
+ "ReadableStreamDefaultController_close") \
+ macro(ReadableStreamDefaultController_enqueue, \
+ ReadableStreamDefaultController_enqueue, \
+ "ReadableStreamDefaultController_enqueue") \
+ macro(ReadableStreamDefaultController_error, \
+ ReadableStreamDefaultController_error, \
+ "ReadableStreamDefaultController_error") \
+ macro(ReadableStreamDefaultReader_cancel, \
+ ReadableStreamDefaultReader_cancel, \
+ "ReadableStreamDefaultReader_cancel") \
+ macro(ReadableStreamDefaultReader_read, \
+ ReadableStreamDefaultReader_read, \
+ "ReadableStreamDefaultReader_read") \
+ macro(ReadableStreamDefaultReader_releaseLock, \
+ ReadableStreamDefaultReader_releaseLock, \
+ "ReadableStreamDefaultReader_releaseLock") \
+ macro(ReadableStreamTee, ReadableStreamTee, "ReadableStreamTee") \
macro(reason, reason, "reason") \
macro(RegExpFlagsGetter, RegExpFlagsGetter, "RegExpFlagsGetter") \
macro(RegExpStringIterator, RegExpStringIterator, "RegExp String Iterator") \
@@ -347,6 +399,7 @@
macro(StarGeneratorNext, StarGeneratorNext, "StarGeneratorNext") \
macro(StarGeneratorReturn, StarGeneratorReturn, "StarGeneratorReturn") \
macro(StarGeneratorThrow, StarGeneratorThrow, "StarGeneratorThrow") \
+ macro(start, start, "start") \
macro(startTimestamp, startTimestamp, "startTimestamp") \
macro(state, state, "state") \
macro(static, static_, "static") \
diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp
index b7d3344b3e..1321be9e9b 100644
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -23,6 +23,7 @@
#include "builtin/Promise.h"
#include "builtin/RegExp.h"
#include "builtin/SelfHostingDefines.h"
+#include "builtin/Stream.h"
#include "builtin/SymbolObject.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakMapObject.h"
@@ -98,6 +99,16 @@ GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key)
case JSProto_WebAssembly:
return !wasm::HasSupport(cx);
+ case JSProto_ReadableStream:
+ case JSProto_ReadableStreamDefaultReader:
+ case JSProto_ReadableStreamBYOBReader:
+ case JSProto_ReadableStreamDefaultController:
+ case JSProto_ReadableByteStreamController:
+ case JSProto_ReadableStreamBYOBRequest:
+ case JSProto_ByteLengthQueuingStrategy:
+ case JSProto_CountQueuingStrategy:
+ return !cx->options().streams();
+
#ifdef ENABLE_SHARED_ARRAY_BUFFER
case JSProto_Atomics:
case JSProto_SharedArrayBuffer:
diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp
index 053b7c44b0..c5deff940c 100644
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -156,6 +156,12 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime)
startAsyncTaskCallback(nullptr),
finishAsyncTaskCallback(nullptr),
promiseTasksToDestroy(mutexid::PromiseTaskPtrVector),
+ readableStreamDataRequestCallback(nullptr),
+ readableStreamWriteIntoReadRequestCallback(nullptr),
+ readableStreamCancelCallback(nullptr),
+ readableStreamClosedCallback(nullptr),
+ readableStreamErroredCallback(nullptr),
+ readableStreamFinalizeCallback(nullptr),
exclusiveAccessLock(mutexid::RuntimeExclusiveAccess),
#ifdef DEBUG
mainThreadHasExclusiveAccess(false),
diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h
index 962f0e1a1e..13523844c1 100644
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -655,6 +655,13 @@ struct JSRuntime : public JS::shadow::Runtime,
JS::FinishAsyncTaskCallback finishAsyncTaskCallback;
js::ExclusiveData<js::PromiseTaskPtrVector> promiseTasksToDestroy;
+ JS::RequestReadableStreamDataCallback readableStreamDataRequestCallback;
+ JS::WriteIntoReadRequestBufferCallback readableStreamWriteIntoReadRequestCallback;
+ JS::CancelReadableStreamCallback readableStreamCancelCallback;
+ JS::ReadableStreamClosedCallback readableStreamClosedCallback;
+ JS::ReadableStreamErroredCallback readableStreamErroredCallback;
+ JS::ReadableStreamFinalizeCallback readableStreamFinalizeCallback;
+
private:
/*
* Lock taken when using per-runtime or per-zone data that could otherwise
diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp
index e9c72d1bf1..e006846752 100644
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -36,6 +36,7 @@
#include "builtin/Promise.h"
#include "builtin/Reflect.h"
#include "builtin/SelfHostingDefines.h"
+#include "builtin/Stream.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
@@ -71,7 +72,6 @@ using JS::AutoCheckCannotGC;
using mozilla::IsInRange;
using mozilla::Maybe;
using mozilla::PodMove;
-using mozilla::Maybe;
static void
selfHosting_WarningReporter(JSContext* cx, JSErrorReport* report)
@@ -1098,9 +1098,9 @@ static bool
intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
- MOZ_ASSERT(args.length() == 5);
+ MOZ_ASSERT(args.length() == 6);
- bool isWrapped = args[4].toBoolean();
+ bool isWrapped = args[5].toBoolean();
Rooted<T*> toBuffer(cx);
if (!isWrapped) {
toBuffer = &args[0].toObject().as<T>();
@@ -1114,11 +1114,12 @@ intrinsic_ArrayBufferCopyData(JSContext* cx, unsigned argc, Value* vp)
}
toBuffer = toBufferObj.as<T>();
}
- Rooted<T*> fromBuffer(cx, &args[1].toObject().as<T>());
- uint32_t fromIndex = uint32_t(args[2].toInt32());
- uint32_t count = uint32_t(args[3].toInt32());
+ uint32_t toIndex = uint32_t(args[1].toInt32());
+ Rooted<T*> fromBuffer(cx, &args[2].toObject().as<T>());
+ uint32_t fromIndex = uint32_t(args[3].toInt32());
+ uint32_t count = uint32_t(args[4].toInt32());
- T::copyData(toBuffer, fromBuffer, fromIndex, count);
+ T::copyData(toBuffer, toIndex, fromBuffer, fromIndex, count);
args.rval().setUndefined();
return true;
@@ -2426,14 +2427,14 @@ static const JSFunctionSpec intrinsic_functions[] = {
intrinsic_PossiblyWrappedArrayBufferByteLength<ArrayBufferObject>, 1,0,
IntrinsicPossiblyWrappedArrayBufferByteLength),
JS_FN("ArrayBufferCopyData",
- intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 5,0),
+ intrinsic_ArrayBufferCopyData<ArrayBufferObject>, 6,0),
JS_FN("SharedArrayBufferByteLength",
intrinsic_ArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("PossiblyWrappedSharedArrayBufferByteLength",
intrinsic_PossiblyWrappedArrayBufferByteLength<SharedArrayBufferObject>, 1,0),
JS_FN("SharedArrayBufferCopyData",
- intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 5,0),
+ intrinsic_ArrayBufferCopyData<SharedArrayBufferObject>, 6,0),
JS_FN("IsUint8TypedArray", intrinsic_IsUint8TypedArray, 1,0),
JS_FN("IsInt8TypedArray", intrinsic_IsInt8TypedArray, 1,0),
@@ -2489,6 +2490,9 @@ static const JSFunctionSpec intrinsic_functions[] = {
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
+ JS_FN("IsReadableStreamBYOBRequest",
+ intrinsic_IsInstanceOfBuiltin<ReadableStreamBYOBRequest>, 1, 0),
+
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),
diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp
index 8e55001d13..44fe3b790d 100644
--- a/js/src/vm/SharedArrayObject.cpp
+++ b/js/src/vm/SharedArrayObject.cpp
@@ -331,15 +331,16 @@ SharedArrayBufferObject::addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSi
}
/* static */ void
-SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer,
- Handle<SharedArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count)
+SharedArrayBufferObject::copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count)
{
MOZ_ASSERT(toBuffer->byteLength() >= count);
+ MOZ_ASSERT(toBuffer->byteLength() >= toIndex + count);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex);
MOZ_ASSERT(fromBuffer->byteLength() >= fromIndex + count);
- jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared(),
+ jit::AtomicOperations::memcpySafeWhenRacy(toBuffer->dataPointerShared() + toIndex,
fromBuffer->dataPointerShared() + fromIndex,
count);
}
diff --git a/js/src/vm/SharedArrayObject.h b/js/src/vm/SharedArrayObject.h
index 05db688184..19048336cb 100644
--- a/js/src/vm/SharedArrayObject.h
+++ b/js/src/vm/SharedArrayObject.h
@@ -146,9 +146,9 @@ class SharedArrayBufferObject : public ArrayBufferObjectMaybeShared
static void addSizeOfExcludingThis(JSObject* obj, mozilla::MallocSizeOf mallocSizeOf,
JS::ClassInfo* info);
- static void copyData(Handle<SharedArrayBufferObject*> toBuffer,
- Handle<SharedArrayBufferObject*> fromBuffer,
- uint32_t fromIndex, uint32_t count);
+ static void copyData(Handle<SharedArrayBufferObject*> toBuffer, uint32_t toIndex,
+ Handle<SharedArrayBufferObject*> fromBuffer, uint32_t fromIndex,
+ uint32_t count);
SharedArrayRawBuffer* rawBufferObject() const;
diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp
index 8ecc14b650..cf7785e34c 100644
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -1438,6 +1438,8 @@ ReloadPrefsCallback(const char* pref, void* data)
bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
+
+ bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
bool unboxedObjects = Preferences::GetBool(JS_OPTIONS_DOT_STR "unboxed_objects");
@@ -1463,7 +1465,8 @@ ReloadPrefsCallback(const char* pref, void* data)
.setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
.setWerror(werror)
.setExtraWarnings(extraWarnings)
- .setArrayProtoValues(arrayProtoValues);
+ .setArrayProtoValues(arrayProtoValues)
+ .setStreams(streams);
JS_SetParallelParsingEnabled(cx, parallelParsing);
JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js
index 7131b144ca..0f3fa7c85c 100644
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1301,6 +1301,9 @@ pref("javascript.options.main_thread_stack_quota_cap", 2097152);
// Dynamic module import.
pref("javascript.options.dynamicImport", true);
+// Streams API
+pref("javascript.options.streams", true);
+
// advanced prefs
pref("advanced.mailftp", false);
pref("image.animation_mode", "normal");
@@ -4525,6 +4528,9 @@ pref("layout.animated-image-layers.enabled", false);
// Abort API
pref("dom.abortController.enabled", true);
+// Streams API
+pref("dom.streams.enabled", true);
+
// Push
pref("dom.push.enabled", false);
pref("dom.push.loglevel", "error");