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 /dom | |
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
Diffstat (limited to 'dom')
59 files changed, 3206 insertions, 1062 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); }; |