diff options
author | Moonchild <moonchild@palemoon.org> | 2023-10-03 17:23:19 +0000 |
---|---|---|
committer | Moonchild <moonchild@palemoon.org> | 2023-10-03 17:23:19 +0000 |
commit | 5d76f359905f10fbd5b4c53ac458e26d22ffe2a1 (patch) | |
tree | 912d084cee32880dad7b06fcd1fdc1fc71ce5b95 | |
parent | 682e5a4727b8bdf33a670c5d08e9cb382f019a57 (diff) | |
parent | 054f135a5455967009587e12c14908904ab4105a (diff) | |
download | uxp-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
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, ¬ByteStream)) + 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, ¬Byob)) + 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"); |